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

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

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

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

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

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

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

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

显然在jkj\leq k时,vv中已经合并的点数不到总合并点数(合并得不够多)。也就是说此时(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;
}

没有更多推荐了,返回首页