CF1554E You

statement

给你一个 \(n\) 个点的树,可以通过以下方式生成一个长度为 \(n\) 的序列 \(a\)

  • 每次在树中选取一个未被标记的节点 \(u\),令 \(a_u\) 等于与节点 \(u\) 相邻的未被标记的节点个数,然后将节点 \(u\) 标记

对于每一个整数 \(k\in[1,n]\),输出符合以下条件的序列 \(a\) 的数量模 \(998244353\) 的值:

  • 序列 \(a\) 可以由给定的树通过上述方式生成;
  • \(\gcd(a_1,a_2,\cdots,a_n)=k\)

Hints

\(2≤n≤10^5\)

solution

看到这种 \(\gcd = k\) ,直接设 \(f(k)\) 表示答案,然后发现 \(f(1)\) 不好算,设 \(g(k)\) 表示序列 \(\gcd\)\(k\) 的倍数的总方案数。

有转移:

\[f(k) = g(k) - \sum_{k\cdot s \le n} f(k\cdot s) \]

然后问题就变成求出一个 \(g\) 了。

发现按每个点不好算,因为会影响到与这个点链接的所有点,将对象改变成每条边。

其实影响序列 \(a\) 的只是由边相连的两个点,所以每条边会使得一条边所链接的其中一个点对应的 \(a_i\) 加一。

所以 \(g(k)\) 就是求出共有多少个合法序列中,每个元素都是 \(k\) 的倍数。

非常容易证明,对于两个序列 \(a\) ,若存在至少一条边所产生贡献的点是不一样的,且两个序列是相同的,那么与原来的图中必定有环存在。

所以 \(g(1) = 2 ^ {n - 1}\)

至此,和原来树的形态没有任何关系。

考虑 \(g(k)\ (k \ge 2)\) 的情况,可以发现,在原来的树中,度数比 \(k\) 小的必定使得它的 \(a\) 变为 \(0\)

归纳的想 \(k = 2\) 的情况,发现只有叶子节点度数小于 \(2\)

再从子树到树归纳的想,若一个叶子节点是 \(k\) 的倍数,那么这个点与父亲连的这一条边一定会让父亲的 \(a\) 加一,否则自己加一,如果做不到,那么 \(g(k) = 0\)

这样也说明了当 \(k \ge 2\) 时,\(g(k) = 0\)\(1\)

但是暴力算是 \(\mathcal O (n ^ 2)\) 的,这样的东西一般都是根号分治,但是这道题的式子是枚举倍数的,存在重复地把一些 \(f\) 加上再减去,可以只枚举质数倍数的 \(f\) ,但这是无关紧要的。

发现 \(g(k) = 0\) 的时候必须满足 \(k\mid (n - 1)\) ,所以只要算这样的所有的 \(g(k)\) 即可。

#include <bits/stdc++.h>
#define forn(i,s,t) for(register int i=(s); i<=(t); ++i)
#define form(i,s,t) for(register int i=(s); i>=(t); --i)
#define rep(i,s,t) for(register int i=(s); i<(t); ++i)
using namespace std;
const int N = 1e5 + 3, Mod = 998244353;
struct Mint {
	int res;
	Mint() {}
	Mint(int a) : res(a) {}
	inline friend Mint operator + (const Mint& A, const Mint& B) {
		return Mint((A.res + B.res >= Mod) ? (A.res + B.res - Mod) : (A.res + B.res));
	}
	inline friend Mint operator - (const Mint& A, const Mint& B) {return A + Mint(Mod - B.res);}
	inline friend Mint operator * (const Mint& A, const Mint& B) {return Mint(1ll * A.res * B.res %Mod);}
	inline friend Mint& operator += (Mint& A, const Mint& B) {return A = A + B;}
	inline friend Mint& operator -= (Mint& A, const Mint& B) {return A = A - B;}
	inline friend Mint& operator *= (Mint& A, const Mint& B) {return A = A * B;}
	inline friend Mint q_pow(Mint p, int k) {
		static Mint res; res = Mint(1);
		for(; k; k >>= 1, p *= p) (k & 1) && (res *= p, 0);
		return res;
	}
};
Mint f[N]; 
struct Lst {
	int dir, nxt;
	Lst() {}
	Lst(int _d, int _n) : dir(_d), nxt(_n) {}
} E[N << 1];
int G[N], cnt, n;
inline void Add(int u, int v) {E[++cnt] = Lst(v, G[u]), G[u] = cnt;}
int dfn[N], ord, fa[N];
void Pre_dfs(int u) {
	for(register int i=G[u], v; i; i=E[i].nxt) {
		v = E[i].dir;
		if(v == fa[u]) continue ;
		fa[v] = u, Pre_dfs(v);
	}
	dfn[++ord] = u;
}
int w[N];
inline bool g(int k) {
	forn(i,1,n) w[i] = 0;
	rep(i,1,n) {
		int u = dfn[i];
		if(w[u] % k) w[u] ++ ;
		else w[fa[u]] ++ ;
		if(w[u] % k) return 0;
	}
	return 1;
}
inline void init() {forn(i,1,n) G[i] = 0; ord = cnt = 0;}
inline void solve() {
	scanf("%d", &n);
	rep(i,1,n) {
		static int u, v;
		scanf("%d%d", &u, &v);
		Add(u, v), Add(v, u);
	}
	Pre_dfs(1);
	form(i,n - 1,1) {
		if((n - 1) % i) f[i] = Mint(0);
		else if(i == 1) f[i] = q_pow(Mint(2), n - 1);
		else f[i] = Mint(g(i));
		for(register int j = i + i; j <= n - 1; j += i)
			f[i] -= f[j];
	}
	f[n] = Mint(0);
	forn(i,1,n) printf("%d%c", f[i].res, " \n"[i == n]);
}
int T;
int main() {
	scanf("%d", &T);
	while(T--) init(), solve();
	return 0;
}
posted @ 2021-09-06 16:06  AxDea  阅读(42)  评论(0)    收藏  举报