[CF1554E] You(数论)

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

题意

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

分析

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

代码如下

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值