傳送門
題目
某人在玩一個非常神奇的遊戲。這個遊戲中有一個左右各\(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;
}