1. 程式人生 > >[ZJOI2015]地震後的幻想鄉

[ZJOI2015]地震後的幻想鄉

cpp ali ios www ots pro display 題意 tde

題面

給一個圖,邊權為 \([0,1]\) 的均勻分布的隨機實數,求期望最小生成樹的最大邊權。

提示:對於 \(n\)\([0,1]\) 之間的隨機變量 \(x_1,x_2,\dots,x_n\) ,第 \(k\) 小的那個的期望值是\(\frac{k}{(n+1)}\)

\(\text{Solution:}\)

將題意寫成數學公式,

\(Kruscal\) 的過程中求:
\[ E = \sum_{i=1}^m\frac{i\times P(加入第i條邊恰好連通)}{m+1} \]
提出上面的式子:
\[ \begin{aligned} E\times (m+1) &= \sum_{i=1}^mi\times P(加入第i條邊恰好連通)\&=\sum_{i=1}^mi\times (P(加入i條邊使圖連通)-P(加入i-1條邊使原圖連通))\&=\sum_{i=1}^mi\times (1-P(加入i條邊使圖不連通)-1+P(加入i-1條邊使原圖不連通))\&=\sum_{i=1}^mi\times (P(加入i-1條邊使原圖不連通) - P(加入i條邊使圖不連通))\\end{aligned} \]


展開,得:
\[ E\times (m+1) = \sum_{i=0}^{m-1}P(加入i條邊使圖不連通) - m\times P(加入m條邊使圖不連通) \]
由於 \(m\times P(加入m條邊使圖不連通) = 0\) (題目保證連通)

\[ E\times (m+1) = \sum_{i=0}^{m-1}P(加入i條邊使圖不連通) \]


\(f[S][i]\)\(G(S)\) 中選 \(i\) 條邊使原圖不連通的方案數.

\(g[S][i]\)\(G(S)\) 中選 \(i\) 條邊使原圖連通的方案數.

\(ecnt[S]\)\(G(S)\) 中所包含的邊數.

然後就可以轉移了

首先顯然有: \(g[S][i] = C_{ecnt[S]}^{i} - f[S][i]\)

由於聯通性的定義為任意兩點可以到達,我們考慮欽定一個點,枚舉它所能到的點集 \(T\) ,以及它不能到達的點集 \(\complement_{S}^T\), 有轉移:

\[ f[S][i] = \sum_{T\subsetneqq S} \sum_{j=0}^{ecnt[T]} g[T][j] \times C_{ecnt[\complement_{S}^T]}^{i-j} \]

然後就做完了。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;

#define LL long long
#define debug(...) fprintf(stderr, __VA_ARGS__)
#define GO debug("GO\n")
inline int rint() {
    register int x = 0, f = 1; register char c;
    while (!isdigit(c = getchar())) if (c == '-') f  = -1;
    while (x = (x << 1) + (x << 3) + (c ^ 48), isdigit(c = getchar()));
    return x * f;
}

const int N = 12, M = 55;

int n, m;
LL graph[N], cnt[1 << N], ecnt[1 << N], f[1 << N][M], g[1 << N][M];
LL C[M][M];

void init() {
    for (int i = 0; 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];
    }
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("xhc.in", "r", stdin);
    freopen("xhc.out", "w", stdout);
#endif
    n = rint(), m = rint();
    init();
    for (int i = 0; i < m; ++ i) {
        int u = rint(), v = rint(); u--, v--;
        graph[u] |= (1 << v);
        graph[v] |= (1 << u);
    }
    for (int i = 0; i < 1 << n; ++ i) {
        for (int j = 0; j < n; ++ j) 
            if (i >> j & 1) 
                ecnt[i] += __builtin_popcount(graph[j] & i);
        ecnt[i] >>= 1;//每條邊算了兩遍
    }

    for (int S = 0; S < 1 << n; ++ S) {
        if (__builtin_popcount(S) == 1) {
            g[S][0] = 1;
            continue;
        }
        for (int T = (S - 1) & S; T; T = (T - 1) & S)//枚舉真子集
            if (T & (S & -S)) {//聯通的定義是兩兩之間互相可以到達,欽定一個點x,枚舉它能到達的點集(T),以及它不能到達的點(S ^ T),這裏欽定為lowbit
                for (int i = 0; i <= ecnt[T] + ecnt[S ^ T]; ++ i) {
                    for (int j = 0; j <= min(ecnt[T], 1ll * i); ++ j)
                        f[S][i] += g[T][j] * C[ecnt[S ^ T]][i - j];
                }
            }
        for (int i = 0; i <= ecnt[S]; ++ i)
            g[S][i] = C[ecnt[S]][i] - f[S][i];
    }
    double ans = 0;
    for (int i = 0; i < m; ++ i)
        ans += 1.0 * f[(1 << n) - 1][i] / C[m][i];
    printf("%.6lf\n", ans / (m + 1));
}

[ZJOI2015]地震後的幻想鄉