Description
你是真的强,你是真的强,你是真的强。
有一颗很强的树,但是没你强,所以这棵树会枯萎。
这棵树枯萎的时候会产生一个强者序列 \(a\) , 具体的强者序列 \(a\) 生成方式如下:
-
每次选择一个没有枯萎的点 \(u\), 然后 \(a_{u}\) 等于与 \(u\) 相邻的, 末枯萎的点数。
-
然后 \(u\) 就会枯萎。
现在对于每一个 \(k \in[1, n]\) 你要求出合法的强者序列 \(a\) 的数量, 满足 \(g c d\left(a_{1}, a_{2}, a_{3}, a_{4}, \cdots\right)=k\) 答案对 \(998244353\) 取模。
\(n\le 10^6,\sum n\le 3\times 10^6\)。
Solution
题目可以转换成对于每条边,指定一个方向,那么 \(a_i\) 就是节点 \(i\) 的入度。那么就有 \(\sum a_i=n-1\)。
当 \(k>1\) 时,可以证明,答案为 0 或 1。
证明:由于 \(k>1\),那么叶子节点一定向他的父亲连边,也就是最底层是确定的,因此我们可以逐步向根走,那么整棵树都是确定的,答案非 0 即 1。
同时由于 \(\sum a_i=n-1\),那么 \(k\) 一定是 \(n-1\) 的约数。但是如果直接找约数时间复杂度是 \(\mathcal O(\sqrt{n})\) 的,再加上遍历的复杂度 \(\mathcal O(n)\),总复杂度 \(\mathcal O(n\sqrt{n})\),不足以通过此题。
思考一下,\(k\) 一定是 \(n-1\) 的某个质因子的倍数,因此如果我们令 \(k\) 为某个质因子,求出此情况下的 \(gcd(a_1,a_2,a_3,\cdots)\),则是真正的 \(k\),也只有这个 \(k\) 是可能有答案的。
\(k=1\) 的答案就是 \(2^{n-1}-sum\),\(sum\) 表示 \(k>1\) 时的答案总数。因为每条边只有两种情况,总情况是 \(2^{n-1}\),有 \(sum\) 种情况的 \(gcd\) 不为 1,那么剩下的就是 \(gcd=1\) 的情况。
Code
#include<cstdio>
#include<cstring>
#define N 1000005
#define mod 998244353
using namespace std;
struct node
{
int to,next;
}a[N<<1];
int pt,T,n,tot,k,x,y,p[N],opt[N],ans[N],head[N],mi[N],c[N];
bool flag,b[N];
int read()
{
int res=0,fh=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') fh=-1;ch=getchar();}
while (ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch-'0'),ch=getchar();
return res*fh;
}
void add(int x,int y)
{
a[++tot].to=y;
a[tot].next=head[x];
head[x]=tot;
}
void dfs(int x,int fa)
{
if (!flag) return;
c[x]=0;
for (int i=head[x];i;i=a[i].next)
{
int v=a[i].to;
if (v==fa) continue;
dfs(v,x);
c[x]+=opt[v];
}
if (c[x]%k==0) opt[x]=1;
else
{
if ((c[x]+1)%k!=0) flag=false;
opt[x]=0;c[x]++;
}
}
int gcd(int x,int y)
{
if (!y||!x) return x+y;
int r=x%y;
while (r) x=y,y=r,r=x%y;
return y;
}
int main()
{
freopen("you.in","r",stdin);
freopen("you.out","w",stdout);
b[1]=true;mi[1]=2;
for (int i=2;i<=1000000;++i)
{
mi[i]=mi[i-1]*2%mod;
if (!b[i]) p[++pt]=i;
for (int j=1;j<=pt;++j)
{
int k=i*p[j];
if (k>1000000) break;
b[k]=true;
if (i%p[j]==0) break;
}
}
T=read();
while (T--)
{
tot=0;
n=read();
for (int i=1;i<=n;++i)
head[i]=ans[i]=0;
for(int i=1;i<n;++i)
{
x=read();y=read();
add(x,y);add(y,x);
}
ans[1]=mi[n-1];
for (int i=1;i<=pt;++i)
{
if (p[i]>n-1) break;
if ((n-1)%p[i]==0)
{
k=p[i];
flag=true;
dfs(1,0);
if (flag)
{
int g=0;
for (int j=1;j<=n;++j)
g=gcd(g,c[j]);
if (g<n&&!ans[g]) ans[g]=1,ans[1]--;
}
}
}
for (int i=1;i<=n;++i)
printf("%d ",ans[i]);
printf("\n");
}
return 0;
}