【BZOJ3925】地震后的幻想乡(ZJOI2015)-概率期望+子集状压DP

测试地址:地震后的幻想乡
做法:本题需要用到概率期望+子集状压DP。
题目要求最小生成树最大边的期望,我们知道这个值等于最大边的期望排名(从小到大) /(m+1) / ( m + 1 ) ,因为提示里说了, m m [0,1]内的随机变量的第 k k 小值的期望为km+1
那么令 L L 为最大边的排名,则有:
ans=E[L]m+1=1m+1x=1mxP(L=x)
得到这个和式,我们变换它的求和顺序,可得:
ans=mx=1P(Lx) a n s = ∑ x = 1 m P ( L ≥ x )
而最大边 x ≥ x 的概率,等于用排名前 x1 x − 1 的边不能使图连通的概率,令这个概率为 T(x1) T ( x − 1 ) ,我们要求:
ans=m1x=0T(x) a n s = ∑ x = 0 m − 1 T ( x )
因为每种 x x 条边的组合作为排名前x的边的概率是相等的,于是 T(x) T ( x ) 实际上就等于,用 x x 条边使得图不能连通的方案数,除以所有x条边的组合数。于是问题变成了如何求用 x x 条边使得图不能连通的方案数。
于是我们有了一个定义状态的思路:令f(i,j)为用(连接点集 i i 内部点的)j条边使得点集 i i 不连通的方案数。我们发现只有这个状态不好转移,因为一般这种连通性状压DP的转移思路都是,通过枚举某个点所在的连通块,来做到不重不漏地枚举。于是我们有另一个状态定义:令g(i,j)为用(连接点集 i i 内部点的)j条边使得点集 i i 连通的方案数,那么令k i i 的一个包含某定点P的真子集,有以下状态转移方程:
f(i,j)=kedge(k)l=0g(k,l)Cjledge(ik) f ( i , j ) = ∑ k ∑ l = 0 e d g e ( k ) g ( k , l ) ⋅ C e d g e ( i − k ) j − l
其中 edge(S) e d g e ( S ) 表示连接点集 S S 内部点的边的数量,那么上式的含义其实就是,枚举定点P所在的连通块,求剩下的点集不和该连通块连接的方案数。而我们发现 f f g的定义是互补的,所以有:
g(i,j)=Cjedge(i)f(i,j) g ( i , j ) = C e d g e ( i ) j − f ( i , j )
这样我们就可以同时转移 f f g了。那么令 all a l l 为全集,答案就为:
ans=1m+1m1x=0f(all,x)Cxedge(all) a n s = 1 m + 1 ∑ x = 0 m − 1 f ( a l l , x ) C e d g e ( a l l ) x
于是我们就解决了这一题,时间复杂度为 O(3nn2) O ( 3 n n 2 )
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,a[60],b[60],edge[1210];
double C[60][60]={0},f[1210][60],g[1210][60];

void init()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d",&a[i],&b[i]);

    C[0][0]=1.0;
    for(int i=1;i<=m;i++)
    {
        C[i][0]=1.0;
        for(int j=1;j<=i;j++)
            C[i][j]=C[i-1][j-1]+C[i-1][j];
    }

    for(int i=1;i<(1<<n);i++)
    {
        edge[i]=0;
        for(int j=1;j<=m;j++)
            if ((i&(1<<(a[j]-1)))&&(i&(1<<(b[j]-1))))
                edge[i]++;
    }
}

void work()
{
    for(int i=1;i<(1<<n);i++)
    {
        for(int j=0;j<=edge[i];j++)
        {
            f[i][j]=0.0;
            int lowbit=i&(-i);
            for(int k=((i-1)&i);k;k=((k-1)&i))
                if (k&lowbit)
                {
                    for(int l=0;l<=edge[k]&&l<=j;l++)
                        f[i][j]+=g[k][l]*C[edge[i-k]][j-l];
                }
            g[i][j]=C[edge[i]][j]-f[i][j];
        }
    }
    double ans=0.0;
    for(int i=0;i<m;i++)
        ans+=f[(1<<n)-1][i]/C[edge[(1<<n)-1]][i];
    ans/=(double)(m+1);
    printf("%.6lf",ans);
}

int main()
{
    init();
    work();

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值