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

版权声明:这是蒟蒻的BLOG,神犇转载也要吱一声哦~ https://blog.csdn.net/Dream_Lolita/article/details/82950336

【题目】
原题地址
给定一棵 n n 个点的树,进行 n 1 n-1 轮操作,每轮操作随机选择一条边 ( u , v ) (u,v) ,将 u , v u,v 两点合成一个点,即删去这两个点后新建一个点 x x ,将原来与 u u v v 连边的点连向 x x ,接着 x x 的编号随机为 u , v u,v 中的一个。
对于每个编号 1 1 ~ n n ,求它最终留下的概率。 n 50 n\leq 50

【题目分析】
我的概率好弱啊。
比赛的时候往直接计算概率上想了很久,然而并没有什么用。
S C SC 同学用这个思路使劲打,一个我们都觉得很对的方法 \dots 过不了样例。

赛后 S C X SCX 同学提供了一个将问题转化为排列组合问题的想法,顺着这个思路往下想(顺便看了下大佬代码),方才得到了一个可过的做法。

【解题思路】
观察合并的过程,实际上类似并查集的合并,我们合并一条边 ( f a , x ) (fa,x) ,相当于将 x x 并入 f a fa 的并查集。设最后留下来的点作为根 r t rt ,那么对于一条边 ( f a , x ) (fa,x) ,它有 0.5 0.5 的贡献,当且仅当合并这条边时, f a fa 已经在 r t rt 的并查集里。

现在考虑这样一个状态: f [ x ] [ i ] f[x][i] 表示以 x x 为根的子树,已经合并了 i 1 i-1 次,且 x x 仍然还在的概率。特别地,对于叶子节点, f [ x ] [ 1 ] = 1 f[x][1]=1

考虑状态转移,设当前节点为 u u ,一堆儿子 v v 。我们枚举 u u 的子树已经被合并了 i 1 i-1 次,那么接下来要考虑的就是在 v v 下多合并了几个节点以及 ( u , v ) (u,v) 这条边是否产生贡献。我们接着枚举 v v 的子树已经被合并了 j 1 j-1 次,在 v v 下多合并了 k k 个节点。

显然在 j k j\leq k 时, v 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;
}

猜你喜欢

转载自blog.csdn.net/Dream_Lolita/article/details/82950336
今日推荐