CF1554E - You(思维,dfs)
题目
给你一颗n个结点的树,需要执行n次以下操作:
- 选择一个未被删除结点u,令\(a_u\)等于与u相邻未被删除的结点数。
- 将结点u从树中删除
也就是说,n次操作完后可以得到一个序列\(a=\{a_1,a_2,...,a_n\}\)。问在所有可能的序列\(a\)中,满足以下条件:
- 序列\(a\)可以由上述n次操作得到
- \(\gcd(a_1,...,a_n)=k\)
的序列有多少个?
输出\(k=1,2,...,n\)的答案。
题解
考虑删点顺序很麻烦,换成考虑边的贡献。对于一条边\((u,v)\),如果先删除点\(u\),边对\(u\)有贡献1;如果先删的是点\(v\),边对\(v\)有贡献1。因此,每条边贡献的一种分配对应一种删除操作生成的序列,这也说明所有可能的生成的序列有\(2^{n-1}\)个。
设\(f_k\)代表有多少生成的序列满足其中每个元素都可以被\(k\)整数。
显然,\(f_1=2^{n-1}\)。
对于\(k>1\),可以用以下方法构造一个方案:
- 以1为根构造有根树,自底向上构造。
- 假设\(u\)的子树的结点都已经分配好满足整除\(k\),对于和\(u\)相连的边没被如果没被分配给子结点必然就要分配给\(u\)结点。假设当前分配给\(u\)结点的的值为\(t\)。
- 如果\(t\)可以被\(k\)整除,什么完成\(u\)的分配;
- 否则只能从\(u\)和父结点相连的边获得一个贡献,得到\(t+1\)。如果\(t+1\)都不能被\(k\)整除,那么必定无解,否则完成分配。
从构造方法可得,对于\(k>1\),\(f_k=0或1\)。
计算所有\(f_k\)的时间复杂度为\(O(n^2)\)。又可以发现,对于\(k\)不整除\(n-1\),\(f_k=0\)。因为\(\sum{a_i}=n-1\),如果\(k|a_i\),那么有\(k|n\)。所以只需要检查\(n\)的所有因子即可。故时间复杂度为\(O(n\sigma_0(n))\)。
最后\(h_k\)为答案,有\(h_k=\sum_\limits{i=1}^{\lfloor\frac{n}{k}\rfloor}{\mu(i)f_{i \cdot k}}\),总时间复杂度为\(O(nlogn+n\sigma_0(n))\)。
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 3e5 + 10;
const int M = 998244353;
const double eps = 1e-5;
vector<int> np[N];
ll f[N], h[N], val[N];
inline ll qpow(ll a, ll b, ll m) {
ll res = 1;
while(b) {
if(b & 1) res = (res * a) % m;
a = (a * a) % m;
b = b >> 1;
}
return res;
}
bool dfs(int p, int fa, int k) {
int d = 0;
val[p] = 0;
for(int nt : np[p]) {
if(nt == fa) continue;
if(!dfs(nt, p, k)) return false;
d++;
}
val[p] += d;
if(val[p] % k != 0) {
if(p == 1 || (val[p] + 1) % k != 0) return false;
else {
val[fa]--;
val[p]++;
}
}
return true;
}
int pri[N], mu[N], cnt;
bool isnp[N];
int main() {
IOS;
mu[1] = 1;
for(int i = 2; i < N; i++) {
if(!isnp[i]) {
pri[cnt++] = i;
mu[i] = -1;
}
for(int j = 0; j < cnt && i * pri[j] < N; j++) {
isnp[i * pri[j]] = 1;
if(i % pri[j] == 0) {
mu[i * pri[j]] = 0;
break;
}
mu[i * pri[j]] = -mu[i];
}
}
int t;
cin >> t;
while(t--) {
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
np[i].clear();
f[i] = h[i] = 0;
}
for(int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
np[u].push_back(v);
np[v].push_back(u);
}
vector<int> d;
for(int i = 1; i * i <= n - 1; i++) {
if((n - 1) % i == 0) {
d.push_back(i);
if((n - 1) / i != i) d.push_back((n - 1) / i);
}
}
f[1] = qpow(2, n - 1, M);
for(int i = 1; i < d.size(); i++) {
f[d[i]] = dfs(1, 0, d[i]);
}
for(int i = 1; i <= n; i++) {
h[i] = f[i];
for(int j = 2; j <= n / i; j++) {
h[i] = (h[i] + mu[j] * f[i * j] + M) % M;
}
cout << h[i] << " \n"[i == n];
}
}
}