[BZOJ3925][Zjoi2015]地震后的幻想乡(期望+状压dp)

不会积分的本蒟蒻真的想不出此题。

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=3925

Solution

显然,如果已经知道了 m 条边的权值,那么可以将边按权值排序,和 Kruskal 一样,从小到大考虑每一条边,如果加入一条边后不形成环就加入这条边。树结构形成(有 n 1 条边被加入)时,最后加入的一条边(最大边)就是需要的修复时间。
根据 HINT 里的说明可以得出,我们只需要求出最大边在这 m 条边中排名的期望值,然后再除以 m + 1 ,就得到了修复时间的期望值。
P ( i ) 表示按随机顺序加入前 i 条边,恰好把图连通的概率。
「恰好」的概念:前 i 1 条边加入时该图不连通,再加入第 i 条边后图连通。

E ( ) = 1 m + 1 i = 1 m i P ( i )

= 1 m + 1 i = 1 m j = i m P ( j )

接下来我们思考 P 的后缀和的实际意义。
容易发现, j = i m P ( j ) 表示至少加入 i 条边后图才连通的概率。
显然,这样等价于加入 i 1 条边后原图不连通的概率。
所以,问题转化为:
对于每个 i [ 0 , m ] ,求出随机选出 i 条边加入后,原图不连通的概率。
看到 n 的范围,可以猜出这是状压 DP :
f [ i ] [ S ] [ 0 ] 表示在点集 S 和点集 S 中的点之间的边构成的子图中,选出 i 条边,不连通点集 S 的方案数。
f [ i ] [ S ] [ 1 ] 表示在点集 S 和点集 S 中的点之间的边构成的子图中,选出 i 条边,连通点集 S 的方案数。
设上面所描述的子图的边数为 s i z e [ S ]
显然:
f [ i ] [ S ] [ 0 ] + f [ i ] [ S ] [ 1 ] = C s i z e [ S ] i

思路要点在转移。思路是:枚举点集 S 的第一个元素 p 所在的连通块(枚举 S 的一个非空真子集 S S 必须包含 p ,并将 S 作为这个连通块包含的点集),然后再枚举这个连通块包含的边数 j (满足 0 j i )。那么点集 S 和点集 S S 之间的任何一对点都不会有连边,并且点集 S S 之间连了剩下的 i j 条边。得出转移:
f [ i ] [ S ] [ 0 ] + = f [ j ] [ S ] [ 1 ] × C s i z e [ S S ] i j

根据 f [ i ] [ S ] [ 0 ] + f [ i ] [ S ] [ 1 ] = C s i z e [ S ] i 就能求出 f [ i ] [ S ] [ 1 ]
这样,
i = f [ i ] [ { 1 , 2 , . . . , n } ] [ 0 ] C m i

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Subset(k, U) for (k = (U - 1) & U; k; k = (k - 1) & U)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll; const int N = 12, M = 102, C = (1 << 10) + 5;
int n, m, Cm, orz[C], otz[C]; bool g[N][N]; ll f[M][C][2], _C[M][M]; double ans;
int main() {
    int i, j, k, h; n = read(); m = read(); For (i, 1, m) g[read()][read()] = 1;
    Cm = (1 << n) - 1; For (i, 1, Cm) {
        orz[i] = 1; while (!((i >> orz[i] - 1) & 1)) orz[i]++;
        For (j, 1, n) if ((i >> j - 1) & 1) For (k, 1, n)
            if (((i >> k - 1) & 1) && g[j][k]) otz[i]++;
    }
    For (i, 0, m) _C[i][0] = 1; For (i, 1, m) For (j, 1, i)
        _C[i][j] = _C[i - 1][j] + _C[i - 1][j - 1];
    For (i, 0, n - 1) f[0][1 << i][1] = 1;
    For (i, 0, Cm) f[0][i][0] = 1 - f[0][i][1]; For (i, 1, m) For (j, 1, Cm) {
        Subset (k, j) if ((k >> orz[j] - 1) & 1) For (h, 0, i) 
            f[i][j][0] += f[h][k][1] * _C[otz[k ^ j]][i - h];
        f[i][j][1] = _C[otz[j]][i] - f[i][j][0];
    }
    For (i, 0, m) ans += 1.0 * f[i][Cm][0] / _C[m][i];
    printf("%.6lf\n", ans / (m + 1)); return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/80905255
今日推荐