题解:P15578 [USACO26FEB] Random Tree Generation G
lfy_syx_172 · · 题解
树形 DP + 快速幂求逆元
本题解中将生成随机带标号树的两步中第一步产生的树称为原树,将输入的树称为新树。
首先考虑在新树中任意指定一个点
于是我们考虑计算任意一个
因为第一步生成的树的总方案数显然为
第二步是简单的,只需要保证重新分配的编号与新树编号一致即可,显然概率为
于是最终生成新树
注意到分母部分是容易计算的,而分子上连乘的一部分并不能简单地计算。
于是考虑用树形 DP 维护,设
如下图所示,当前 DP 到
于是就可以顺利进行 DP 了,对于每个
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10,mod=1e9+7;
int n,f[N],siz[N];
vector<int> g[N];
int fpow(int a,int b)
{
int res=1;
a%=mod;
while(b)
{
if(b&1) res=(res*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return res;
}
int inv(int x)
{
return fpow(x,mod-2);
}
void dfs(int u,int fa)
{
siz[u]=1;
for(int v:g[u])
{
if(v==fa) continue;
dfs(v,u);
siz[u]+=siz[v];
}
}
void dp(int u,int fa)
{
for(int v:g[u])
{
if(v==fa) continue;
f[v]=f[u]*(n-siz[v])%mod*inv(siz[v])%mod;
dp(v,u);
}
}
void solve()
{
cin>>n;
for(int i=1;i<=n;i++) g[i].clear();
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
memset(siz,0,sizeof siz);
memset(f,0,sizeof f);
dfs(1,0);
f[1]=1;
for(int i=1;i<=n;i++) f[1]=(f[1]*siz[i])%mod;
dp(1,0);
int ans=0,fac=1;
for(int i=1;i<=n-1;i++) fac=(fac*i)%mod;
ans=inv(fac);
int res=0;
for(int i=1;i<=n;i++) res=(res+inv(f[i]))%mod;
ans=(ans*res)%mod;
cout<<ans<<"\n";
}
signed main()
{
int T;
cin>>T;
while(T--) solve();
}