[ZJOI2015]地震后的幻想乡

这题太毒瘤了不写题解了,前往观看\(->\%\%\%AuBao\):

https://www.cnblogs.com/HNYLMSTea/p/10606564.html

自我认为最精髓的一点是: \(P(i-1联通)+P(i恰好联通)=P(i联通)\) 的转化

总之就是各种转化,最后变成一个可求的东西

我的注释版代码:

#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ls p<<1 
#define rs p<<1|1
using namespace std;
typedef long long ll;
const int mxn=1e5+5;
int n,m,cnt,hd[mxn];

inline int read() {
	char c=getchar(); int x=0,f=1;
	while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();}
	while(c<='9'&&c>='0') {x=(x<<3)+(x<<1)+(c&15);c=getchar();}
	return x*f;
}
inline void chkmax(int &x,int y) {if(x<y) x=y;}
inline void chkmin(int &x,int y) {if(x>y) x=y;}

struct ed {
	int to,nxt;
}t[mxn<<1];

inline void add(int u,int v) {
	t[++cnt]=(ed) {v,hd[u]}; hd[u]=cnt;
}

ll dot[mxn],cnte[mxn],f[mxn][60],g[mxn][60],C[60][60];

void init() {
	for(int i=0;i<=55;++i) C[i][0]=C[i][i]=1;
	for(int i=1;i<=55;++i) 
		for(int j=1;j<=55;++j) 
			C[i][j]=C[i-1][j-1]+C[i-1][j];
}

int main()
{
	n=read(); m=read(); init(); int u,v;
	for(int i=1;i<=m;++i) {
		u=read(); v=read(); --u,--v;
		dot[v]|=(1<<u); dot[u]|=(1<<v);
	}
	for(int i=0;i<(1<<n);++i) {
		for(int j=0;j<n;++j) 
			if(i>>j&1) 
				cnte[i]+=__builtin_popcount(dot[j]&i);
		cnte[i]>>=1; //预处理状态的边数	
	}
	for(int S=0;S<(1<<n);++S) {
		if(__builtin_popcount(S)==1) {
			g[S][0]=1;
			continue ;
		}
		for(int T=(S-1)&S;T;T=(T-1)&S) {
			if(T&(S&-S)) { 
			//每次只枚举一个点(这里是lowbit)能到达的所有点集,这样一定不会算重
				for(int i=0;i<=cnte[T]+cnte[S^T];++i) {
					for(int j=0;j<=min(cnte[T],1ll*i);++j) 
						f[S][i]+=g[T][j]*C[cnte[S^T]][i-j]; 
						//dp方程,图上组合计数的套路
				}
			}
		}
		for(int i=0;i<=cnte[S];++i) 
			g[S][i]=C[cnte[S]][i]-f[S][i]; //补集直接算
	}
	double ans=0;
	for(int i=0;i<m;++i) 
		ans+=1.0*f[(1<<n)-1][i]/C[m][i];
    printf("%.6lf",ans/(m+1));
	return 0;
}

posted @ 2019-03-30 15:08  cloud_9  阅读(104)  评论(0)    收藏  举报