[CF1060F]Shrinking Tree[树dp+组合计数]

题意

你有一棵 \(n\) 个点的树,每次会随机选择树上的一条边,将两个端点 \(u,v\) 合并,新编号随机为 \(u,v\)。问最后保留的编号分别为 \(1\)\(n\) 的概率。

\(n\leq 50\)

分析

  • 考虑枚举钦定一个编号为 \(ans\) 之后以他为根跑一次树dp。

  • 思考一下操作的执行过程。首先,操作连接 \(rt\) 和他的儿子 \(v\) 的边时,必须保留 \(rt\) 的编号,然后合并掉 \(v\) ,可以看成是 \(rt\) 将他的编号传递给了 \(v\) (\(rt\) 变成了一个大点)。那么此时对于 \(v\) 子树内的编号变化就是一个子问题了。

  • \(f_{u,i}\) 表示当 \(rt\) 下放到 \(u\) 时, \(u\) 的子树边还有 \(i\) 条没有合并,所有删边方案最后保留 \(rt\) 的概率和。
    \(g_{u,i}\) 表示当 \(rt\) 下放到 \(u\) 的父亲时, \(u\) 的子树边+返祖边还有 \(i\) 条没有合并,所有删边方案最后保留 \(rt\) 的概率和。

  • 考虑合并 \(u\) 的儿子 \(v\)\(u \rightarrow v\) 这条边可以在 2 个不同的时间段合并。

1.在编号下放到 \(u\) 之后。假设下放到 \(u\)\(v\) 子树内有 \(x\) 条边,那么在删除 \(u \rightarrow v\) 之后 \(v\) 的子树内有 \(\leq x\) 条边,所以 \(g_{v,x}+=0.5*\sum_{i=0}^{min({son}_v-1,x)}f_{v,i}\) ,因为此时保留 \(rt\) 的概率是 \(0.5\)

2.在编号下放到 \(u\) 之前。所以下放到 \(u\)\(u\)\(v\) 已经看成是一个点,等价于下放到 \(v\)\(u \rightarrow v\) 可以在删除 \(v\) 子树内的 \({son}_v-1-x\) 条边中的任何空隙删除,所以 \(g_{v,x}+=f_{v,x}*({son}_v-x)\)

  • 考虑儿子 \(x\)\(y\) 之间的合并,发现他们之间的删边顺序互不影响,容易得到:

\[f_{u,i}=\sum_{a=0}^{{son}_x}\sum_{b=0}^{{son}_y}g_{x,a}*g_{y,b}*\binom{a+b}{a}\binom{{son}_x-a+{son}_y-b}{{son}_x-a} \]

  • 最后的答案除以 \((n-1)!\) 即可。

  • 总时间复杂度为 \(O(n^4)\)

代码

#include<bits/stdc++.h>
using namespace std;
#define go(u) for(int i=head[u],v=e[i].to;i;i=e[i].lst,v=e[i].to)
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define pb push_back
typedef long long LL;
inline int gi(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch))	{if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
	return x*f;
}
template<typename T>inline bool Max(T &a,T b){return a<b?a=b,1:0;}
template<typename T>inline bool Min(T &a,T b){return b<a?a=b,1:0;}
typedef double db;
const int N=54;
int n,edc;
int head[N],son[N];
db f[N][N],fac[N],g[N];
struct edge{
	int lst,to;
	edge(){}edge(int lst,int to):lst(lst),to(to){}
}e[N*2];
void Add(int a,int b){
	e[++edc]=edge(head[a],b),head[a]=edc;
	e[++edc]=edge(head[b],a),head[b]=edc;
}
db C(int n,int m){
	return fac[n]/fac[m]/fac[n-m];
}
void dfs(int u,int fa){
	f[u][0]=1;son[u]=1;
	go(u)if(v^fa){
		dfs(v,u);int tot=son[u]-1+son[v];
		fill(g,g+tot+1,0);
		for(int a=son[u]-1;~a;--a)
		for(int b=son[v];~b;--b)
		g[a+b]+=f[u][a]*f[v][b]*C(a+b,a)*C(tot-a-b,son[v]-b);
		son[u]+=son[v];
		rep(a,0,son[u]-1) f[u][a]=g[a];
	}
	if(fa){
		fill(g,g+son[u]+1,0);
		for(int a=son[u]-1;~a;--a){
			g[a]+=f[u][a]*(son[u]-a);
			for(int b=a+1;b<=son[u];++b)
			g[b]+=f[u][a]*0.5;
		}
		rep(a,0,son[u]) f[u][a]=g[a];
	}
}
int main(){
	n=gi();
	rep(i,1,n-1) Add(gi(),gi());
	fac[0]=1;
	rep(i,1,n) fac[i]=fac[i-1]*i;
	rep(i,1,n){
		memset(f,0,sizeof f);
		dfs(i,0);
		printf("%.10lf\n",f[i][n-1]/fac[n-1]);
	}
	return 0;
}
posted @ 2018-12-02 21:35  fwat  阅读(371)  评论(2编辑  收藏  举报