缩树游戏(tree)
正在解决难以理解的地方
题意
小\(X\) 同学觉得树上问题太毒瘤了,于是决定将树上的边删去,最终变成一个点。
现在有一个\(n\)个节点的树,他的游戏是这样的:
- 从剩下的所有边中等概率随机选中一条边\(T\)。
- 将这条边删去,若这条边相连的两个点编号为\(u\)和\(v\),新建一个点\(x\),这个点与所有与\(u\)和\(v\)相邻的点有边,最后删去\(u\)和\(v\)及与它们相连的边,\(x\)的编号等概率随机命名为\(u\)或\(v\)。不断重复上述步骤,直到只剩下一个点。
现在小\(X\) 想知道,对于每个编号,最后剩下该编号的点的概率。
Sol
考虑树形DP。以1为根,其余同理
设方程\(f[i][j]\)表示当前搜到搜索标号为\(i\)的节点,该节点删去\(j\)条边,并保证根节点未被删除的方案数。
显然,一个不删是一种方案,所以\(f[i][0]=1\)。
先考虑链的情况:只有单子树。
枚举子树状态:假设子树的根标号为\(x\),得到\(f[x][0—siz[x]]\),现在要求\(f[i][0—siz[i]]\)
枚举统计\(f[i][j∈[0,siz[x]]]\):每次枚举\(f[x][k∈[0,siz[x])]\),对于\(j,k\)分类讨论:
- \(k<j\),表示选择\(x\)子树中的\(k\)条边,并且由于\(i\)选择的边数有剩,所以根转移到了\(x\),需要将答案乘2的逆元,得到方程\(f[i][j]+=inv2*f[x][k]\);
- \(k==j\),表示刚好选择了\(x\)子树中的\(j\)条边(\(≥0\)),所以没有转移根,而\(i\)到\(x\)的边在这个序列中的插入有\(siz[x]-j\)种,得到方程\(f[i][j]+=f[x][k]*(siz[x]-j)\)
- \(k>j\),选择溢出,所以不需要考虑。
相信你已经懵了,总之就是在树形DP的时候要先统计子节点的子树的答案(由于需要特殊计算)
接下来的部分是多子树的合并:假设已经得到了前面的所有子树答案,现在新加入一个新子树求答案增量。
(下面的内容\(i,j,k\)不与上面对等,是分开计算的)
假设以前计算的答案是\(f[i][j]\),当前枚举到\(x\)子树,选择\(g[k]\)(假设\(g[k]\)表示之前枚举\(x\)的子节点数得到的答案),那么更新的答案就是\(f[i][j+k]+=f[i][j]*g[k]*C(j+k,j)*C(x+y-j-k,x-j)\)(\(x,y\)分别表示树\(i\)的原边数和现在\(x\)子树的边数),表示答案乘上对应的选择方案。
最后的答案就是\(f[1][n-1]\)。最后将方案数除\((n-1)!\)即可,剩下的就是数论基操。
然后就愉快地结束辣!
(感觉懵是正常现象,因为我写的时候是晕的)
Code
注意我代码中的x就是方程中的\(i\),而\(to\)才是方程中的\(x\),不要降智搞混了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=500010,p=998244353;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=0;c=getchar();}
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
return f?x:-x;
}
struct edge
{
int to,next;
}e[maxn];
int h[maxn],ei;
inline void add(int x,int y)
{
e[++ei]=(edge){y,h[x]};
h[x]=ei;return;
}
int siz[60],f[60][60],g[60],tool[60];
int n,inv2,facinv,C[60][60];
inline void dfs(int x,int fa)
{
siz[x]=f[x][0]=1;
for(int i=h[x];i;i=e[i].next)
{
int to=e[i].to;
if(to==fa)continue;
dfs(to,x);
for(int j=0;j<=siz[to];j++)
{
g[j]=0;
for(int k=0;k<siz[to];k++)
{
if(k<j)g[j]+=inv2*f[to][k]%p;
else if(k==j)g[j]+=f[to][j]*(siz[to]-j);
if(g[j]>=p)g[j]-=p;
}
}
for(int j=0;j<siz[x]+siz[to];j++)tool[j]=0;
for(int j=0;j<siz[x];j++)
{
for(int k=0;k<=siz[to];k++)
{
tool[j+k]=(tool[j+k]+f[x][j]*g[k]%p*C[j+k][j]%p*C[siz[x]+siz[to]-j-k-1][siz[x]-j-1]%p)%p;
// cout<<f[x][j]<<" "<<g[k]<<" "<<C[j+k][j]<<" "<<C[siz[x]+siz[to]-j-k-1][siz[x]-j-1]<<endl;
}
}
siz[x]+=siz[to];
for(int j=0;j<siz[x];j++)f[x][j]=tool[j];
}
return;
}
inline int ksm(int x,int mi)
{
int ans=1;
while(mi)
{
if(mi&1)ans=ans*x%p;
x=x*x%p;mi>>=1;
}
return ans;
}
signed main()
{
// freopen("tree.in","r",stdin);
// freopen("tree.out","w",stdout);
n=read();inv2=499122177;
for(int i=1;i<n;i++)
{
int x=read(),y=read();
add(x,y);add(y,x);
}
facinv=1;
for(int i=2;i<n;i++)facinv=facinv*i%p;
facinv=ksm(facinv,p-2);
C[0][0]=1;
for(int i=1;i<=n;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%p;
}
for(int i=1;i<=n;i++)
{
dfs(i,0);
printf("%lld\n",f[i][n-1]*facinv%p);
}
return 0;
}

浙公网安备 33010602011771号