P3343 [ZJOI2015]地震后的幻想乡

传送门

由提示可以知道,如果把图中的边从小到大依次加入,在加入第 $k$ 条边时图恰好联通,那么期望花费为 $\frac{k}{m+1}$

注意到期望花费和加入边数成正比,发现可以看成每一条加入后不使图联通的边的贡献之和,每条不使图联通的边的贡献即为 $\frac{1}{m+1}$

那么如果能算出 $f[i]$ 表示加入第 $i$ 条边时图仍然不连通的概率,那么它对答案的贡献就是 $\frac {1}{m+1}$,期望贡献即为 $\frac{f[i]}{m+1}$

那么枚举每一条不使图联通的边,可以得到 $ans=\frac{1}{m+1}\sum_{k=0}^{m}f[k]$(注意这里有算到第 $0$ 条边的贡献)

然后现在的问题就是求这个东西的概率,考虑求出合法方案数,然后除以总方案数即可得到概率

设 $f[S][i]$ 表示当前点集为 $S$ ,加入了 $i$ 条边仍然不联通的方案数,同理设 $g[S][i]$ 表示联通的方案数

那么显然有 $f[S][i]+g[S][i]=\binom{D_S}{i}$ ,其中 $D_S$ 表示点集 $S$ 的总边数

现在考虑 $f$ 的转移,显然考虑枚举这个不连通块中的某个联通块子集,那么容易想到转移 $f[S][i]=\sum_{T \in S}\sum_{j=0}^{min(i,D_T)} g[T][j]\binom{D_{S-T}}{i-j}$

但是发现这样对于 $f[S][i]$ 的每个方案都多算了那个方案中联通块个数次

然后这里就有一个神仙操作,钦定某个点一定在 $T$ 中,那么这样就不会重复算到一种方案了(因为这个方案的其他联通块都不会枚举到了)

然后算完 $f[S][i]$ 那么 $g[S][i]=\binom{D_S}{i}-f[S][i]$

最后 $ans=\frac{1}{m+1}\sum_{k=0}^{m}\frac{f[U][k]}{\binom{m}{k}}$,显然 $U$ 表示全集,$\binom{m}{k}$ 就是总方案

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
typedef double db;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=11,M=107;
int n,m,cnt[(1<<N)+7],a[M],b[M];
ll C[M][M],f[(1<<N)+7][M],g[(1<<N)+7][M];
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
        a[i]=read()-1,b[i]=read()-1;
    int mx=(1<<n)-1;
    for(int i=1;i<=mx;i++)
        for(int j=1;j<=m;j++)
            if((i>>a[j])&1 && (i>>b[j])&1) cnt[i]++;
    for(int i=0;i<=m;i++)
    {
        C[i][0]=1;
        for(int j=1;j<=i;j++)
            C[i][j]=C[i-1][j]+C[i-1][j-1];
    }
    for(int o=1;o<=mx;o++)
        for(int j=0;j<=cnt[o];j++)
        {
            for(int op=(o-1)&o;op;op=(op-1)&o)
                if(op&(o&-o))
                    for(int k=0;k<=min(j,cnt[op]);k++)
                        f[o][j]+=g[op][k]*C[cnt[o^op]][j-k];
            g[o][j]=C[cnt[o]][j]-f[o][j];
        }
    db ans;
    for(int i=0;i<=m;i++) ans+=1.0*f[mx][i]/C[m][i];
    ans/=m+1; printf("%.6lf\n",ans);
    return 0;
}

 

posted @ 2019-10-20 14:33  LLTYYC  阅读(200)  评论(0编辑  收藏  举报