【题解】CF1060F Shrinking Tree 概率树形dp 好题!

20 篇文章 0 订阅
14 篇文章 1 订阅
题面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);
	}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值