[THUWC2017]隨機二分圖

傳送門

題目

  某人在玩一個非常神奇的遊戲。這個遊戲中有一個左右各\(n\)個點的二分圖,圖中的邊會按照一定的規律隨機出現。

  爲了描述這些規律,某人將這些邊分到若干個組中。每條邊或者不屬於任何組 (這樣的邊一定不會出現),或者只屬於一個組。

  有且僅有以下三類邊的分組:

  這類組每組只有一條邊,該條邊恰好有\(50\%\)的概率出現。

  這類組每組恰好有兩條邊,這兩條邊有\(50\%\)的概率同時出現,有\(50\%\)的概率同時不出現。

  這類組每組恰好有兩條邊,這兩條邊恰好出現一條,各有\(50\%\)的概率出現。

  組和組之間邊的出現都是完全獨立的。

  某人現在知道了邊的分組和組的種類,想要知道完美匹配數量的期望是多少。你能幫助她解決這個問題嗎?

  數據範圍詳見原題。

題解

  這是一道與概率期望有關的一道題,比較難。

  最暴力的做法就是將所有的邊的組合枚舉,然後判斷完美匹配數。太蠢了。

  枚舉每個匹配,然後看這個匹配在多少種可能中出現過。當然,要判斷匹配中是否存在衝突:比如說第三類邊中,兩個都存在就衝突了。複雜度\(\text{O}(n!\times m)\)

  下面是正解:看到\(n\leq 15\),我們發現這個很可以狀壓:以\(n\)對點是否匹配上爲狀態,發現這樣狀態數爲\(\begin{aligned}\sum_{i=0}^{n}{n\choose i}={2n \choose n}\end{aligned}\)種,當\(n=15\)時將近\(10^8\)。事實上大部分數據都跑不滿。

  下一步呢?對以上每一組邊進行分析。假設所有邊都在第一類,則完美匹配數的期望實際上求的是這些邊能夠組成的完美匹配數\(\times (\dfrac{1}{2})^n\),落實到每一條匹配邊,會貢獻\(50\%\)的概率。

  對於第二種情況,強制了兩條邊要麼同時出現,要麼同時不出現,如果仍然按每條匹配邊概率獨立來看的話,一條邊在匹配中的概率是\(50\%\),兩條邊都在匹配中的概率是\(25\%\),兩條邊都不在匹配中的概率爲\(25\%\),最後一種並不重要,它並不會貢獻到答案裏。如果一條邊在匹配中(注意這不等於只有這條邊出現),則另一條邊一定出現了且不在匹配之中,也就是說兩條邊一定都出現了,會貢獻\(50\%\)的概率;而兩條邊都在匹配中同理也是會貢獻\(50\%\)的概率,但現在卻是\(25\%\),我們建立一個四元邊,選它的概率爲\(25\%\)(即一下子匹配掉這兩條邊)。從概率的角度來看,這兩個事件是並列且獨立的,加起來就是\(50\%\)了;

  對於第三種情況,與第二種情況很類似,只要將\(25\%\)調成\(-25\%\)即可。(因爲兩條邊不可能會同時出現)

  最後小細節:爲了避免重複計算,匹配需要按結點順序枚舉。

代碼

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

#define ll long long
#define ull unsigned long long
#define min(a, b) (a) < (b) ? (a) : (b)
#define max(a, b) (a) > (b) ? (a) : (b)
#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
#define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
#define per0(i, a) for (int i = a-1; ~i; --i)
#define chkmax(a, b) a = max(a, b)
#define chkmin(a, b) a = min(a, b)

const int maxn = 16;
const int P = 1e9 + 7;

inline int read() {
    int w = 0, f = 1; char c;
    while (!isdigit(c = getchar())) c == '-' && (f = -1);
    while (isdigit(c)) w = w * 10 + (c ^ 48), c = getchar();
    return w * f;
}

int n, m;

std::unordered_map<int, int> f[1<<maxn]; // 用哈希表來優化內存
std::vector<std::pair<int, int> > E[maxn]; // 左邊每個結點對應的邊

int qpow(int a, int b) {
    int res = 1;
    for (int i = a; b; i = 1ll*i*i%P, b >>= 1)
        if (b & 1) res = 1ll*res*i%P;
    return res;
}

const int inv2 = qpow(2, P-2), inv4 = qpow(4, P-2);

void adde(int S, int P) { rep(i, 0, n-1) if (S&(1<<i)) return (void)E[i].push_back(std::make_pair(S, P)); } // 避免四元邊重複計算,一定要return

int dp(int S) {
    if (!S) return 1; // 所有邊匹配上返回
    int L = S&((1<<n)-1), R = S>>n;
    if (f[L].count(R)) return f[L][R]; // 記憶化
    int ans = 0, p = 0;
    while (!(S&(1<<p))) p++; // 找出編號最小的還沒匹配上的結點
    rep0(i, E[p].size()) if ((E[p][i].first & S) == E[p][i].first) ans = (1ll*E[p][i].second*dp(S^E[p][i].first) + ans)%P; // 枚舉S的子事件,然後乘上對應的概率
    return f[L][R] = ans; // 返回答案
}

int main() {
    n = read(), m = read();
    rep(i, 1, m) {
        int opt = read(), u = read(), v = read(), S = (1<<u-1) | (1<<v+n-1);
        adde(S, inv2); // 加入一條邊
        if (opt) {
            int u1 = read(), v1 = read(), S1 = (1<<u1-1) | (1<<v1+n-1);
            adde(S1, inv2); // 加入另一條邊
            if (!(S&S1)) adde(S ^ S1, opt == 1 ? inv4 : P-inv4); // 加入四元邊。根據情況看概率
        }
    }

    printf("%d", 1ll*dp((1<<(n<<1))-1)*qpow(2, n)%P); // 注意最終答案爲E*2^n%1e9+7

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章