缩树游戏(tree)

正在解决难以理解的地方

题意

\(X\) 同学觉得树上问题太毒瘤了,于是决定将树上的边删去,最终变成一个点。
现在有一个\(n\)个节点的树,他的游戏是这样的:

  1. 从剩下的所有边中等概率随机选中一条边\(T\)
  2. 将这条边删去,若这条边相连的两个点编号为\(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;
}
posted @ 2021-09-22 17:50  wwlvv  阅读(102)  评论(0)    收藏  举报