题面link
题目描述(中文)
对于一棵有 nn 个节点的树 TT 。当 TT 的节点数多于一个时,反复执行以下操作:
等概率地选取 TT 中的一条边。
收缩选取的边:即合并这条边连接的两个点 uu 和 vv 。得到的新点的编号等概率地从 uu 和 vv 中选取一个。
当这个过程结束时, TT 只剩一个节点了,它的编号可能是 1,…,n中的任意一个数 。对于每个编号,请输出最终得到这个编号的概率。
输入格式(中文):
第一行一个正整数 n (1≤n≤50 )。
题解 学习了大佬的题解
思路首先是只考虑i号点成为最终点的方案,而它怎么被筛掉的不考虑!
因为树的形态在变化,必须用一个使树形态固定转化才好转移。
一个错误的想法
一开始想直接求x点的子树中剩i条边的概率,和x点子树中根合并i次的概率。每次把当前子树根固定,但是这样如果当前子树的根提前和上面合并了,树的形态还是无法预测
于是我们就应该把当前的总根rt,也就是要求答案的节点放到子树中来。
dp状态记录F(x,i)总根到x的子树中,当前还有i条边没有合并的概率。
这个dp的思想是对所有删边序列统计每种序列的概率和,所以转移的时候要乘组合数。最后要除(n - 1)!
另外,关于边的dp,转移分成两种:
在x处两棵合并:
把x的子树放到fa处考虑,枚举(fa,x)的边在x变为i前还是后删除。
具体转移见前面的题解。
总结:
这样树上的期望题非常巧。但是我感觉我还是没有深入的理解,以后复习的时候还要仔细想想!
一开始的错误想法的确需要写一写才能知道错在哪。并且这种题很好写,只要想清楚(不知道为什么错)就可以写!
写题的时候必须把式子完全推清楚,否则无法调!
方法:
把树的形态固定,而不能记录会“压缩”的树
把删边序列的每种方案的概率进行统计。转移的时候要乘组合数,把已经删了的边和未删的边分别组合!
只考虑当前关键点保留的概率,不考虑它不合法是什么样子
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define rvc(i,S) for(register int i = 0 ; i < (int)S.size() ; i++)
#define rvcd(i,S) for(register int i = ((int)S.size()) - 1 ; i >= 0 ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) (x&(-x))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int,int> pr;
const ll inf = 2e18;
const int N = 3e6 + 10;
const int maxn = 2020;
const ll mod = 666623333;
ld g[60],f[60][60],pow_[60];
struct node{
int next,to;
}e[maxn * 2];
int head[maxn],cnt;
int n,sz[maxn];
ld fac[maxn];
inline void adde(int x,int y){
e[++cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt;
}
inline ld C(int n,int m){
if ( n < m || m < 0 || n < 0 ) return 0;
return fac[n] / fac[m] / fac[n - m];
}
void dfs(int x,int fa){
sz[x] = 1;
f[x][0] = 1;
fore(i,x){
if ( e[i].to == fa ) continue;
dfs(e[i].to,x);
memcpy(g,f[x],sizeof(g));
memset(f[x],0,sizeof(g));
rep(j,0,sz[x]) rep(k,0,sz[e[i].to]){
f[x][j + k] += g[j] * f[e[i].to][k] * C(j + k,j) * C(sz[x] + sz[e[i].to] - j - k - 1,sz[e[i].to] - k);
}
sz[x] += sz[e[i].to];
}
if ( fa ){
memcpy(g,f[x],sizeof(g));
memset(f[x],0,sizeof(g));
rep(i,0,sz[x]){
rep(j,0,i - 1){
f[x][i] += 0.5 * g[j];
}
f[x][i] += (sz[x] - i) * g[i];
}
}
}
void solve(int x){
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
dfs(x,0);
printf("%.8lf\n",(double)(f[x][n - 1] / fac[n - 1]));
}
int main(){
scanf("%d",&n);
rep(i,1,n - 1){
int x,y;
scanf("%d %d",&x,&y);
adde(x,y) , adde(y,x);
}
fac[0] = 1;
rep(i,1,n) fac[i] = fac[i - 1] * i;
rep(i,1,n){
solve(i);
}
}