【bzoj3925】地震后的幻想乡 子集Dp

       在题目中,假设有恰好k条边使图联通,那么答案就是k/(m+1)。

       如果我们令f[i][j]表示子集为i,已经选了j条边时集合不连通的方案数,令g[i][j]表示连通的方案数,那么显然可以得到g[i][j]+f[i][j]=C(m,i中包含的边的条数)。

       然后计算f[i],可以利用任意一个i中的点x,然后用包含x的连通块连通的方案数*剩下的边都不在包含x的连通块中的概率得到。

       最后统计答案时,考虑添加i条边不连通时,相比于i-1条边不连通,期望的最小瓶颈生成树增加k/(m+1)-(k-1)/(m+1)=1/m+1,再乘上概率就是期望的答案增加的值。因此这时给答案带来的贡献为f[all][i]/C(m,i)*1/(m+1)。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define N 2105
using namespace std;

int n,m,e[105],bin[105],sz[N],sum[N];
ll c[105][105],f[N][105],g[N][105];
int main(){
	scanf("%d%d",&n,&m); int i,j;
	bin[0]=1; for (i=1; i<=n; i++) bin[i]=bin[i-1]<<1;
	for (i=1; i<=m; i++){
		int x,y; scanf("%d%d",&x,&y);
		e[x]|=bin[y-1]; e[y]|=bin[x-1];
		for (j=1; j<bin[n]; j++)
			if ((j&bin[x-1]) && (j&bin[y-1])) sum[j]++;
	}
	c[0][0]=1;
	for (i=1; i<=m; i++){
		c[i][0]=1;
		for (j=1; j<=i; j++) c[i][j]=c[i-1][j]+c[i-1][j-1];
	}
	for (i=1; i<bin[n]; i++){
		sz[i]=sz[i>>1]+(i&1);
		if (sz[i]==1){ g[i][0]=1; continue; }
		int t=i&-i,x,y;
		for (j=(i-1)&i; j; j=(j-1)&i) if (j&t){
			int k=i^j;
			for (x=0; x<=sum[j]; x++)
				for (y=0; y<=sum[k]; y++)
					f[i][x+y]+=g[j][x]*c[sum[k]][y];
		}
		for (j=0; j<=sum[i]; j++) g[i][j]=c[sum[i]][j]-f[i][j];
	}
	double ans=0;
	for (i=0; i<=m; i++)
		ans+=(double)f[bin[n]-1][i]/c[m][i];
	printf("%.6f\n",ans/(m+1));
	return 0;
}


by lych

2016.2.29

你可能感兴趣的:(动态规划,概率,期望,状压dp,子集Dp)