【概率/组合数学】CF1060F Shrinking Tree

【题目】
原题地址
给定一棵 n n n个点的树,进行 n − 1 n-1 n1轮操作,每轮操作随机选择一条边 ( 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 n50

【题目分析】
我的概率好弱啊。
比赛的时候往直接计算概率上想了很久,然而并没有什么用。
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 i1次,且 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 i1次,那么接下来要考虑的就是在 v v v下多合并了几个节点以及 ( u , v ) (u,v) (u,v)这条边是否产生贡献。我们接着枚举 v v v的子树已经被合并了 j − 1 j-1 j1次,在 v v v下多合并了 k k k个节点。

显然在 j ≤ k j\leq k jk时, 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值