考虑枚举每一个点来计算答案,枚举到一个点时,将该点看做是树的根。设 \(f_{x,i}\) 为在 \(x\) 的子树内进行删边,只考虑后 \(i\) 条边的编号分配,其他边任意分配,且根节点编号最后仍为 \(x\) 的几率之和。得 \(x\) 的最终答案为 \(\frac{f_{x,n-1}}{(n-1)!}\)。c++
考虑合并子树,将 \(x\) 的儿子 \(y\) 合并到当前子树中,发现须要给 \(y\) 加上一条到 \(x\) 的边,用新子树的 \(DP\) 值来转移。设新的 \(DP\) 值为 \(g_i\),其定义和 \(f_{x,i}\) 相同。git
考虑如何计算 \(g_i\),枚举 \((x,y)\) 这条边在倒数第 \(j\) 步被删掉。当 \(i \geqslant j\) 时,这里要求删掉 \((x,y)\) 时必须保留 \(x\),有 \(\frac{1}{2}\) 的几率,以前的边的选择是任意的,以后必须保留 \(x\),这里删去了 \((x,y)\),保留 \(y\) 和保留 \(x\) 等价,所以将 \(\frac{1}{2}f_{y,j-1}\) 贡献到 \(g_i\)。当 \(i<j\) 时,\((x,y)\) 这条边不用考虑编号分配,所以将 \(f_{y,i}\) 贡献到 \(g_i\)。spa
合并子树时就是将 \(f_{x,i}g_j\binom{i+j}{i}\binom{siz_x-1-i+siz_y-j}{siz_x-1-i}\) 贡献到 \(f_{x,i+j}\)。code
#include<bits/stdc++.h> #define maxn 110 using namespace std; template<typename T> inline void read(T &x) { x=0;char c=getchar();bool flag=false; while(!isdigit(c)){if(c=='-')flag=true;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} if(flag)x=-x; } int n; int siz[maxn]; double fac[maxn],f[maxn][maxn],g[maxn]; struct edge { int to,nxt; edge(int a=0,int b=0) { to=a,nxt=b; } }e[maxn]; int head[maxn],edge_cnt; void add(int from,int to) { e[++edge_cnt]=edge(to,head[from]),head[from]=edge_cnt; } double C(int n,int m) { return fac[n]/fac[m]/fac[n-m]; } void dfs(int x,int fa) { f[x][0]=siz[x]=1; for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(y==fa) continue; dfs(y,x); for(int j=0;j<=siz[y];++j) for(int k=1;k<=siz[y];++k) f[0][j]+=k<=j?f[y][k-1]/2:f[y][j]; for(int j=siz[x]-1;j>=0;--j) for(int k=siz[y];k>=0;--k) g[j+k]+=f[x][j]*f[0][k]*C(j+k,j)*C(siz[x]-1-j+siz[y]-k,siz[x]-1-j); siz[x]+=siz[y]; for(int i=0;i<siz[x];++i) f[x][i]=g[i]; memset(g,0,sizeof(g)),memset(f[0],0,sizeof(f[0])); } } int main() { read(n); for(int i=1;i<n;++i) { int x,y; read(x),read(y); add(x,y),add(y,x); } fac[0]=1; for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i; for(int i=1;i<=n;++i) { memset(f,0,sizeof(f)),dfs(i,0); printf("%.10lf\n",f[i][n-1]/fac[n-1]); } return 0; }