【题目】
原题地址
给定一棵
n
n
n个点的树,进行
n
−
1
n-1
n−1轮操作,每轮操作随机选择一条边
(
u
,
v
)
(u,v)
(u,v),将
u
,
v
u,v
u,v两点合成一个点,即删去这两个点后新建一个点
x
x
x,将原来与
u
u
u或
v
v
v连边的点连向
x
x
x,接着
x
x
x的编号随机为
u
,
v
u,v
u,v中的一个。
对于每个编号
1
1
1~
n
n
n,求它最终留下的概率。
n
≤
50
n\leq 50
n≤50
【题目分析】
我的概率好弱啊。
比赛的时候往直接计算概率上想了很久,然而并没有什么用。
S
C
SC
SC同学用这个思路使劲打,一个我们都觉得很对的方法
…
\dots
…过不了样例。
赛后 S C X SCX SCX同学提供了一个将问题转化为排列组合问题的想法,顺着这个思路往下想(顺便看了下大佬代码),方才得到了一个可过的做法。
【解题思路】
观察合并的过程,实际上类似并查集的合并,我们合并一条边
(
f
a
,
x
)
(fa,x)
(fa,x),相当于将
x
x
x并入
f
a
fa
fa的并查集。设最后留下来的点作为根
r
t
rt
rt,那么对于一条边
(
f
a
,
x
)
(fa,x)
(fa,x),它有
0.5
0.5
0.5的贡献,当且仅当合并这条边时,
f
a
fa
fa已经在
r
t
rt
rt的并查集里。
现在考虑这样一个状态: f [ x ] [ i ] f[x][i] f[x][i]表示以 x x x为根的子树,已经合并了 i − 1 i-1 i−1次,且 x x x仍然还在的概率。特别地,对于叶子节点, f [ x ] [ 1 ] = 1 f[x][1]=1 f[x][1]=1。
考虑状态转移,设当前节点为 u u u,一堆儿子 v v v。我们枚举 u u u的子树已经被合并了 i − 1 i-1 i−1次,那么接下来要考虑的就是在 v v v下多合并了几个节点以及 ( u , v ) (u,v) (u,v)这条边是否产生贡献。我们接着枚举 v v v的子树已经被合并了 j − 1 j-1 j−1次,在 v v v下多合并了 k k k个节点。
显然在 j ≤ k j\leq k j≤k时, v v v中已经合并的点数不到总合并点数(合并得不够多)。也就是说此时 ( u , v ) (u,v) (u,v)这条边必然会因为合并产生贡献。
因为各棵子树之间是相互独立的,而合并的节点贡献和未合并的节点顺序也是独立的,我们只需要用隔板法就能计算出所有的排列方案,具体可以参见代码。
最后我们再在全局下除以一个阶乘就可以了。
【参考代码】(话说谁知道怎么改代码块啊,好丑啊)
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long double ldb;
const int N=55;
int n,tot,siz[N],head[N];
ldb g[N],f[N][N],C[N][N];
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
struct Tway{int v,nex;}e[N<<1];
void add(int u,int v)
{
e[++tot]=(Tway){v,head[u]};head[u]=tot;
e[++tot]=(Tway){u,head[v]};head[v]=tot;
}
void init()
{
for(int i=0;i<N;++i)
{
C[i][0]=C[i][i]=1;
for(int j=1;j<i;++j) C[i][j]=C[i-1][j]+C[i-1][j-1];
}
n=read();
for(int i=1;i<n;++i) add(read(),read());
}
void dfs(int x,int fa)
{
siz[x]=f[x][1]=1;
for(int l=head[x];l;l=e[l].nex)
{
int v=e[l].v;
if(v==fa) continue; dfs(v,x);
for(int i=0;i<=siz[x]+siz[v];++i) g[i]=0;
for(int i=1;i<=siz[x];++i) for(int j=1;j<=siz[v];++j) for(int k=0;k<=siz[v];++k)
{
ldb tmp=f[x][i]*f[v][min(j,k+1)]*(j<=k?0.5:1);
g[i+k]+=tmp*C[i+k-1][k]*C[siz[x]-i+siz[v]-k][siz[v]-k];
}
for(int i=0;i<=siz[x]+siz[v];++i) f[x][i]=g[i]; siz[x]+=siz[v];
//for(int i=0;i<=siz[x];++i) printf("%lf ",(db)g[i]);puts("");
}
}
void solve()
{
ldb fc=1; for(int i=1;i<n;++i) fc*=(ldb)i;
for(int i=1;i<=n;++i) dfs(i,0),printf("%.9lf\n",(db)(f[i][n]/fc));
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("CF1060F.in","r",stdin);
freopen("CF1060F.out","w",stdout);
#endif
init();
solve();
return 0;
}