bzoj3925 [Zjoi2015]地震后的幻想乡

题目描述

傲娇少女幽香是一个很萌很萌的妹子,而且她非常非常地有爱心,很喜欢为幻想乡的人们做一些自己力所能及的事情来帮助他们。 这不,幻想乡突然发生了地震,所有的道路都崩塌了。现在的首要任务是尽快让幻想乡的交通体系重新建立起来。

幻想乡一共有n个地方,那么最快的方法当然是修复n-1条道路将这n个地方都连接起来。 幻想乡这n个地方本来是连通的,一共有m条边。现在这m条边由于地震的关系,全部都毁坏掉了。每条边都有一个修复它需要花费的时间,第i条边所需要的时间为ei。地震发生以后,由于幽香是一位人生经验丰富,见得多了的长者,她根据以前的经验,知道每次地震以后,每个ei会是一个0到1之间均匀分布的随机实数。并且所有ei都是完全独立的。

现在幽香要出发去帮忙修复道路了,她可以使用一个神奇的大魔法,能够选择需要的那n-1条边,同时开始修复,那么修复完成的时间就是这n-1条边的ei的最大值。当然幽香会先使用一个更加神奇的大魔法来观察出每条边ei的值,然后再选择完成时间最小的方案。 幽香在走之前,她想知道修复完成的时间的期望是多少呢?

输入格式

第一行两个数n,m,表示地方的数量和边的数量。其中点从1到n标号。 接下来m行,每行两个数a,b,表示点a和点b之间原来有一条边。 这个图不会有重边和自环。

输出格式

一行输出答案,四舍五入保留6位小数。

样例输入

5 4
1 2
1 5
4 3
5 3

样例输出

0.800000

说明

提示:

(以下内容与题意无关,对于解题也不是必要的。)

对于n个[0,1]之间的随机变量x1,x2,...,xn,第k小的那个的期望值是k/(n+1)。

样例解释:

对于第一个样例,由于只有4条边,幽香显然只能选择这4条,那么答案就是4条边的ei中最大的数的期望,由提示中的内容,可知答案为0.8。

数据范围:

对于所有数据:n<=10, m<=n(n-1)/2, n,m>=1。

对于15%的数据:n<=3。

另有15%的数据:n<=10, m=n。

另有10%的数据:n<=10, m=n(n-1)/2。

另有20%的数据:n<=5。

另有20%的数据:n<=8。


分析

比较困难的期望dp。

先把式子列出来。根据提示,设最小生成树的最大边为全图第$i$小的边的概率为$P_1(i)$,则:

$$E=\sum_{i=1}^{m}P_1(i)\cdot \frac{i}{m+1}$$

$$(m+1)\cdot E=\sum_{i=1}^{m}P_1(i)\cdot i$$

右边的式子好像很眼熟,与整数期望公式长得一模一样,所以把式子化成:

$$(m+1)\cdot E=\sum_{i=1}^{m}P_1(x\ge i)$$

发现$P_1(x\ge i)$好像还是不好求。考虑它的意义:最小生成树的最大边$\ge$第$i$小的边的概率。这个概率等于用前$i-1$小的边不能使图连通的概率。

设$P_2(i)$表示用前$i$小的边不能使图连通的概率,则:

$$(m+1)\cdot E=\sum_{i=0}^{m-1}P_2(i)$$

现在的问题就是要求出$P_2(i)$。求概率可以先求方案数$f(i)$,然后除以总方案数$C_m^i$(因为最小的$i$条边是哪$i$条是不确定的)。

求$f(i)$可以使用状压dp,设$f(s,i)$表示选$i$条边使点集$s$仍不连通的方案数。

为了不重不漏地统计,先在$s$中随便取出一个点记为$u$,然后枚举和$u$连通的点集$t$,分别计算方案数并相加。

那么,我们还需要一个数组$g(s,i)$,表示选$i$条边使点集$s$连通的方案数。则:

$$f(s,i)=g(t,j)\cdot C_{cnt(s-t)}^{i-j}$$

相应地:

$$g(s,i)=C_{cnt(s)}^{i} - f(s,i)$$

最后: $$E=\frac{1}{m+1}\cdot \sum_{i=0}^{m-1}\frac{f(all,i)}{C_m^i}$$

代码

#include <bits/stdc++.h>
typedef long long ll;

const int N = 12;
const int S = 1 << N;
const int M = 50;

int n, m;
int bin[N];
int bcnt[S];
int e[N], ecnt[S];
ll f[S][M], g[S][M];
ll c[M][M];

int lb(int x) {
    return x & -x;
}

int main() {
    scanf("%d%d", &n, &m);

    bin[0] = 1;
    for (int i = 1; i <= n; i++)
        bin[i] = bin[i-1] << 1;

    for (int i = 1; i < bin[n]; i++)
        bcnt[i] = bcnt[i-lb(i)] + 1;

    for (int u, v, i = 1; i <= m; i++) {
        scanf("%d%d", &u, &v);
        u--; v--;
        e[u] |= bin[v];
        e[v] |= bin[u];
    }

    for (int i = 0; i < bin[n]; i++) {
        for (int k = 0; k < n; k++) {
            if ((i & bin[k]) == 0) continue;
            ecnt[i] += bcnt[e[k]&i];
        }
        ecnt[i] >>= 1;
    }

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

    for (int i = 1; i < bin[n]; i++) {
        int t = lb(i);
        for (int j = (i-1)&i; j; j = (j-1)&i) {
            if ((j & t) == 0) continue;
            for (int a = 0; a <= ecnt[j]; a++)
                for (int b = 0; b <= ecnt[i-j]; b++)
                    f[i][a+b] += g[j][a] * c[ecnt[i-j]][b];
        }
        for (int j = 0; j <= ecnt[i]; j++)
            g[i][j] = c[ecnt[i]][j] - f[i][j];
    }

    double ans = 0;
    int all = bin[n] - 1;
    for (int i = 0; i < m; i++)
        ans += (double)f[all][i] / c[m][i];
    ans /= m+1;
    printf("%.6lf\n", ans);
    return 0;
}


转载请注明出处。


评论列表,共 0 条评论

    暂无评论

发表评论