题意
给定一棵树,每次可以删一个点,一个点的权值为删除该点时周围没被删的点的个数。对于一种删点方式,会得到一个序列
{
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
1≤n≤105,∑n≤3×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
k∣gcd 的 的方案数,那么
g
k
=
f
k
−
∑
k
∣
k
′
,
k
′
≠
k
g
k
′
g_k=f_k-\sum\limits_{k|k',k'\neq k}g_{k'}
gk=fk−k∣k′,k′=k∑gk′。
首先可以知道,
f
1
=
2
n
−
1
f_1=2^{n-1}
f1=2n−1。
然后考虑求
f
k
(
k
≥
2
)
f_k(k\ge 2)
fk(k≥2)。可以发现,叶子节点的权值要么是
0
0
0,要么是
1
1
1,因为
k
≥
2
k\ge 2
k≥2,所以叶子节点的权值一定是
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=1∑nai=n−1,因此
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,n−1)∣n−1。因此,我们只需要对所有
d
∣
n
−
1
d|n-1
d∣n−1 求
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(n−1))。其中,
σ
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;
}