[CF1554E] You(数论)

本文介绍了一种解决关于树删点问题的方法,通过拓扑排序和容斥原理,计算给定树中每种删点方式得到特定gcd值的方案数。关键步骤包括确定叶子节点权值、利用递推公式求gcd方案数,以及利用因子个数优化时间复杂度。

题意

给定一棵树,每次可以删一个点,一个点的权值为删除该点时周围没被删的点的个数。对于一种删点方式,会得到一个序列 {an}\{a_n\}{an}
对于 k∈[1,n]k\in [1,n]k[1,n],求 gcd⁡(a1,a2,..an)=k\gcd(a_1,a_2,..a_n)=kgcd(a1,a2,..an)=k 的方案数。
其中,1≤n≤105,∑n≤3×1051\le n\le 10^5,\sum n\le 3\times10^51n105,n3×105

分析

可以将问题转化为:对于每一条边 (u,v)(u,v)(u,v),要么给 uuu 加一,要么给 vvv 加一,求 gcd⁡=k\gcd=kgcd=k 的方案数。同时,对于任意一种删点方案,都会得到唯一的 {an}\{a_n\}{an}
考虑套路地容斥,设 gkg_kgkgcd⁡=k\gcd=kgcd=k 的方案数,fkf_kfkk∣gcd⁡k|\gcdkgcd 的 的方案数,那么 gk=fk−∑k∣k′,k′≠kgk′g_k=f_k-\sum\limits_{k|k',k'\neq k}g_{k'}gk=fkkk,k=kgk
首先可以知道,f1=2n−1f_1=2^{n-1}f1=2n1
然后考虑求 fk(k≥2)f_k(k\ge 2)fk(k2)。可以发现,叶子节点的权值要么是 000,要么是 111,因为 k≥2k\ge 2k2,所以叶子节点的权值一定是 000。那么一个叶子会给他父亲贡献 111。这启发我们可以从叶子节点开始进行拓扑排序,一层一层地确定每个点的权值。对于某一层的一个叶子节点,假设其权值目前为 vvv,那么其要么变成 vvv,要么变成 v+1v+1v+1。根据简单的数学知识,我们可以知道,vvvv+1v+1v+1 至多只有一个是 kkk 的倍数。因此,对于一个 fkf_kfk,其方案数是确定的,要么为 000,要么为 111。我们可以 O(n)O(n)O(n) 来确定 fkf_kfk
同时,由于 ∑i=1nai=n−1\sum\limits_{i=1}^n a_i=n-1i=1nai=n1,因此 gcd(a1,a2,...,an)=gcd(a1,..,an,∑ai)=gcd(a1,..,an,n−1)∣n−1gcd(a_1,a_2,...,a_n)=gcd(a_1,..,a_n,\sum a_i)=gcd(a_1,..,a_n,n-1)|n-1gcd(a1,a2,...,an)=gcd(a1,..,an,ai)=gcd(a1,..,an,n1)n1。因此,我们只需要对所有 d∣n−1d|n-1dn1fdf_dfd 就行了。
时间复杂度为 O(nlogn+nσ0(n−1))O(nlogn+n\sigma_0(n-1))O(nlogn+nσ0(n1)。其中,σ0(n)\sigma_0(n)σ0(n)nnn 的因子个数,最多有 128128128 个。

代码如下

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) x.begin(), x.end()
int main() {
#ifdef local
    freopen("../in.txt", "r", stdin);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    const int maxn = 1e5;
    vector<vector<int> > d(maxn + 1);
    for(int i = 1; i <= maxn; i++){
        for(int j = i; j <= maxn; j += i){
            d[j].push_back(i);
        }
    }
    int T;
    cin >> T;
    while(T--){
        int n;
        cin >> n;
        vector<vector<int> > E(n + 1);
        vector<int> f(n + 1), g(f), du(f);
        for(int i = 1, u, v; i < n; i++){
            cin >> u >> v;
            E[u].push_back(v);
            E[v].push_back(u);
            du[u]++, du[v]++;
        }
        function<int(int)> check = [&](int x){
            queue<int> q;
            vector<int> w(n + 1), vis(w), d(du);
            for(int i = 1; i <= n; i++) if(d[i] == 1) q.push(i);
            while(!q.empty()){
                int u = q.front();
                q.pop();
                if(vis[u]) continue;
                vis[u] = 1;
                for(int& v: E[u]){
                    if(vis[v]) continue;
                    --d[v];
                    if(w[u] % x == 0) w[v]++;
                    else w[u]++;
                    if(d[v] == 1) q.push(v);
                }
            }
            for(int i = 1; i <= n; i++) if(w[i] % x != 0) return 0;
            return 1;
        };
        for(int x: d[n - 1]) if(x != 1) f[x] = check(x);
        const int mod = 998244353;
        int two = 1;
        for(int i = n - 1; i >= 1; i--){
            two += two;
            if(two >= mod) two -= mod;
            g[i] = f[i];
            if(i == 1) g[i] = two;
            for(int j = i + i; j <= n - 1; j += i) g[i] -= g[j];
            if(g[i] < 0) g[i] += mod;
        }
        for(int i = 1; i <= n; i++) cout << g[i] << ' ';
        cout << '\n';
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值