[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;
}