do_while_true

一言(ヒトコト)

「题解」Codeforces 1554E You

问题可以转化成给每个边定向,\(a_i\) 变成了 \(i\) 点的入度。

因为给每个点定向必然有个点的入度为 \(0\),从这个点开始删即为一种合法删点方案;对于每个删点方案,每删一个点就把相邻的边都定向到自己,这样本质不同的删点方案一定当且仅当存在一个点的入度不同。这样构造了一个双射,完成了问题的转化。

所以答案的总和为 \(2^{n-1}\)

入度的总数为 \(n-1\),所以一定只有 \(n-1\) 的因数答案才不为 \(0\)

考虑如果 \(k>1\),怎样判断答案是不是 \(0\)?叶子是可以直接确定边要往上的,因为不能出现入度为 \(1\),这样可以把叶子删去,删去叶子之后的叶子,挂着它的边也能定向,然后这个叶子也可以删去...这样不断做拓扑排序,即可以构造出一个方案,看这个方案满不满足条件即可。

注意到这个方案数唯一的,所以 \(k>1\) 时直接拓扑排序来判断即可。

直接这么做时间复杂度是 \(\mathcal{O}(n\sqrt n)\),有一个显著的优化是如果只判素因子就行,再把判素因子得到的 \(\gcd\) 的 ans 加上 \(1\)

\(k=1\) 的答案的话,用答案的总数 \(2^{n-1}\) 减去其它所有的答案就行。复杂度是 \(\mathcal{O}(n \omega (n))\)\(\omega (n)\)\(n\) 的素因子个数。

#include<iostream>
#include<cstdio>
#include<queue>
template <typename T> T Max(T x, T y) { return x > y ? x : y; }
template <typename T> T Min(T x, T y) { return x < y ? x : y; }
template <typename T>
T& read(T& r) {
	r = 0; bool w = 0; char ch = getchar();
	while(ch < '0' || ch > '9') w = ch == '-' ? 1 : 0, ch = getchar();
	while(ch >= '0' && ch <= '9') r = r * 10 + (ch ^ 48), ch = getchar();
	return r = w ? -r : r;
}
typedef long long ll;
const ll mod = 998244353;
ll qpow(ll x, ll y) {
	ll sumq = 1;
	while(y) {
		if(y & 1) sumq = sumq * x % mod;
		 x = x * x % mod;
		 y >>= 1;
	}return sumq;
}
#define int long long 
const int N = 1000010;
int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }
int n, head[N], ent, ans[N], a[N], in[N], in_[N], b[N];
bool vis[N];
struct Edge {
	int to, next;
}e[N << 1];
inline void add(int x, int y) {
	e[++ent].to = y; e[ent].next = head[x]; head[x] = ent;
}
std::queue<int>q;
int check(int x) {
	for(int i = 1; i <= n; ++i) b[i] = a[i] = 0, in[i] = in_[i], vis[i] = 0;
	for(int i = 1; i <= n; ++i)
		if(in[i] == 1) {
			q.push(i);
		}
	while(!q.empty()) {
		int y = q.front();q.pop(); vis[y] = 1;
		for(int i = head[y]; i;i = e[i].next) {
			int v = e[i].to;
			if(vis[v] == 1) continue ;
			if(a[y] == 0) ++a[v], a[v] %= x, ++b[v];
			else ++a[y], a[y] %= x, ++b[y];
			--in[v];
			if(in[v] == 1) {
				q.push(v);
			}
		}
	}
	int g = b[1];
	for(int i = 2; i <= n; ++i) g = gcd(g, b[i]);
	return g;
}
void solve() {
	read(n);
	for(int i = 1; i <= n; ++i) ans[i] = in_[i] = 0, head[i] = 0;
	ent = 0;
	for(int i = 1, u, v; i < n; ++i) read(u), read(v), add(u, v), add(v, u), in_[u]++, in_[v]++;
	int m = n-1;
	for(int i = 1; i * i <= m; ++i)
		if(m % i == 0) {
			int t = check(i);
			if(t % i == 0) ans[t] = 1;
			while(i != 1 && m % i == 0) m /= i;
		}
	if(m > 1) {
		int t = check(m);
		if(t % m == 0) ans[t] = 1;
	}
	ans[1] = qpow(2, n-1);
	for(int i = 2; i <= n; ++i) ans[1] -= ans[i];
	for(int i = 1; i <= n; ++i) printf("%lld ", ans[i]);
	puts("");
}
signed main() {
	int T; read(T);
	while(T--)
		solve();
	return 0;
}
posted @ 2021-08-16 09:47  do_while_true  阅读(37)  评论(0编辑  收藏  举报