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\) 的倍数的总方案数。
有转移:
然后问题就变成求出一个 \(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;
}

浙公网安备 33010602011771号