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];
		}
	}
}
posted @ 2021-09-08 20:24  limil  阅读(26)  评论(0编辑  收藏  举报