传送门

题意:

给定一个n点m边的无向图,没有重边和自环,每条边的权值为 [0,1] [ 0 , 1 ] 之间的随机变量,求最小生成树中最大边的期望权值。

n10,mn(n1)2 n ≤ 10 , m ≤ n ( n − 1 ) 2

Solution:

题意其实可以转化为选前k小的那些边使图恰好联通,求k的期望

答案即为 k(m+1) k ( m + 1 )

那么我们只要分别求出选1-m条边使图恰好联通的概率,再把他们加起来即可得到k

概率可以通过合法方案除总方案数来得出:

num[S] n u m [ S ] 表示两个端点都在S内的边的个数

f[S][i] f [ S ] [ i ] 表示只考虑S这个集合的点,在其中选了i条边,这些点不连通的方案数

g[S][i] g [ S ] [ i ] 表示只考虑S这个集合的点,在其中选了i条边,这些点连通的方案数

那么 f[S][i]+g[S][i]=Cinum[S] f [ S ] [ i ] + g [ S ] [ i ] = C n u m [ S ] i

那么应该如何转移才能使得不会算重呢?这里有一个惯用的套路:每次固定一个点集中的点,枚举包含这个点的联通块的大小

依据上面的条件我们可以得到:

f[S][i+j]=TSg[T][i]Cjnum[ST] f [ S ] [ i + j ] = ∑ T ⊂ S g [ T ] [ i ] ∗ C n u m [ S − T ] j

注意所有的T都必须要包含我们所固定的点,注意这是真子集

设all是全集

最后的答案就是 mi=0f[all][i]Cim(m+1) ∑ i = 0 m f [ a l l ] [ i ] C m i ∗ ( m + 1 )

这样我们每次转移是枚举子集,复杂度 O(3nm) O ( 3 n m )

代码:

#include<cstdio>
#include<iostream>
using namespace std;
int num[1<<10],x[110],y[110];
int n,m,E[110][110];
bool vis[1<<10];
long long f[1<<10][100],g[1<<10][100],C[110][110];
int main()
{
    scanf("%d%d",&n,&m);
    C[0][0]=1;
    for (int i=1;i<=45;i++)
    {
        C[0][i]=1;
        for (int j=1;j<=i;j++)
            C[j][i]=C[j-1][i-1]+C[j][i-1];
    }
    for (int i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]);
    for (int i=0;i<(1<<n);i++)
        for (int j=1;j<=m;j++)
            if (((i>>x[j]-1)&1)&&((i>>y[j]-1)&1)) num[i]++;
    //for (int i=1;i<(1<<n);i++) f[i][0]=1;
    for (int i=0;i<n;i++) vis[1<<i]=1,g[1<<i][0]=1;
    for (int S=1;S<(1<<n);S++)
    {
        if (vis[S]==1) continue;
        int lowbit=(S&(-S));
        for (int k=(S-1)&S;k;k=(k-1)&S)
            if (k&lowbit)
            {
                for (int i=0;i<=num[S];i++)
                    for (int j=0;j<=num[S-k];j++)
                        f[S][i+j]+=g[k][i]*C[j][num[S-k]];
            }
        for (int i=0;i<=num[S];i++)
            g[S][i]=C[i][num[S]]-f[S][i];
        }
    double ans=0;
// for (int i=0;i<(1<<n);i++){
   
// for (int j=0;j<=m;j++)
// cout<<f[i][j]<<" ";cout<<num[i]<<":"<<i<<endl;}
    for (int i=0;i<=m;i++) ans+=1.0*f[(1<<n)-1][i]/C[i][m];
    printf("%.6f",ans/(m+1));
}