statement
给你一个 (n) 个点的树,可以通过以下方式生成一个长度为 (n) 的序列 (a):
- 每次在树中选取一个未被标记的节点 (u),令 (a_u) 等于与节点 (u) 相邻的未被标记的节点个数,然后将节点 (u) 标记。
对于每一个整数 (kin[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) 的时候必须满足 (kmid (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, "
"[i == n]);
}
int T;
int main() {
scanf("%d", &T);
while(T--) init(), solve();
return 0;
}