「ZJOI2015」地震后的幻想乡 解题报告

本文详细解析了ZJOI2015地震后幻想乡问题,探讨了无向图最小生成树最大边权期望的算法。介绍了通过枚举边权相对大小、使用Kruskal算法及利用特定数学提示解决复杂问题的方法。

「ZJOI2015」地震后的幻想乡

想了半天,打开洛谷题解一看,最高票是_rqy的,一堆密密麻麻的积分差点把我吓跑。

据说有三种解法,然而我只学会了一种最辣鸡的凡人解法。


题意:给一个无向图\(G\),边权为\([0,1]\)间的实数,求这个图的最小生成树的最大边权期望。

提示:对于 \(n\)\([0,1]\) 之间的随机变量 \(x_1,x_2,\dots,x_n\),第 \(k\) 小的那个的期望值是 \(\frac{k}{n+1}\)


考虑使用这个提示来帮助解题。

首先有一个暴力做法,枚举边权的相对大小,然后做最小生成树,kruskal得到一棵树时拿提示算一下

这个想法启发我们钦定一个边集\(S\)作为前\(|S|\)小,如果这个边集加入第\(|S|\)小这条边时恰好使图联通,我们就可以算它的贡献是\(\frac{|S|}{m+1}\),如果我们还能算出它的方案并除上总方案,我们就可以得到它的概率,所以考虑去统计这个方案。

恰好联通这个条件并不好统计,我们转换一下,可以变成

恰好联通方案=加之前不连通方案-加之后不连通方案

然后比较自然的可以考虑压一个子集去做\(dp\)

\(f_{S,i},g_{S,i}\)分别表示点集为\(S\),用了\(i\)条边,且点集联通/不连通的方案数,设\(d_S\)为点集\(s\)在图\(G\)中的边数

显然有
\[ g_{S,i}+f_{S,i}=\binom{d_S}{i} \]
考虑\(f\)的递推,我们枚举\(s\)的子集,并且钦定某个点\(k\)一定在子集里,有转移
\[ f_{S,i}=\sum_{k\in T\subset S}\sum_{j=0}^{d_T}g_{T,j}\binom{d_{S-T}}{i-j} \]
然后最后考虑如何统计答案,设\(U\)为全集,按照之前说的,答案为
\[ \sum_{k=1}^{m+1}\frac{k}{m+1}\times \frac{f_{U,k-1}-f_{U,k}}{\binom{d_u}{k}} \]
化简一下
\[ \frac{1}{m+1}\sum_{k=1}^m\frac{f_{U,k}}{\binom{d_U}{k}} \]


Code:

#include <cstdio>
#include <cctype>
#include <algorithm>
using std::min;
template <class T>
void read(T &x)
{
    x=0;char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) x=x*10+c-'0',c=getchar();
}
double C[51][51],f[1<<10][51],g[1<<10][51];
int yuu[1<<10],dew[1<<10],n,m;
int main()
{
    read(n),read(m);
    for(int u,v,i=1;i<=m;i++)
    {
        read(u),read(v);
        ++dew[(1<<u-1)|(1<<v-1)];
    }
    for(int s=1;s<1<<n;s++)
        for(int t=s;t;t=t-1&s)
            yuu[s]+=dew[t];
    C[0][0]=1;
    for(int i=1;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 s=1;s<1<<n;s++)
    {
        for(int i=0;i<=yuu[s];i++)
        {
            for(int t=s-1&s;t;t=t-1&s)
                if(t&(s&-s))
                    for(int j=0;j<=min(i,yuu[t]);j++)
                        f[s][i]+=g[t][j]*C[yuu[s^t]][i-j];
            g[s][i]=C[yuu[s]][i]-f[s][i];
        }
    }
    double ans=0;
    for(int i=0;i<=m;i++) ans+=f[(1<<n)-1][i]/C[m][i];
    ans/=m+1.0;
    printf("%.6f\n",ans);
    return 0;
}

2019.3.5

转载于:https://www.cnblogs.com/butterflydew/p/10477044.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值