1 圖的匹配
1.1 基礎知識
1.1.1 匹配的定義
對於一個無向圖 \(G = (V,E)\),定義一個集合 \(M \sube E\) 是這張圖的一個匹配當且僅當對於不存在 \(u,v,w \in V\) 使得 \((u,v) \in M\) 且 \((u,w) \in M\)。
一個匹配的大小定義為所含邊數的多少。
最大匹配指一張圖中最大的匹配。
考慮到一般圖的匹配比較難,我們一下討論的都是二分圖的匹配。
定義一個二分圖 \(G=(X,Y,E)\)。
1.1.2 增廣軌
對於一個匹配 \(M\),如果存在一條路徑滿足:
-
起點和終點均是未匹配點。
-
路徑上的邊恰好為 \(\not\in M, \in M, \not\in M ... \not\in M\) 這樣交錯的。
這條路徑稱為當前匹配下的一條增廣軌。
1.1.3交錯軌
交錯軌只要求起點未匹配,終點任意。
1.2 最大匹配演算法
1.2.1 樸素最大匹配演算法
最大匹配與增廣軌
定理: 一個二分圖 \(G\) 的匹配 \(M\) 不存在增廣軌 \(\iff\) \(M\) 是最大匹配。
證明:
如果存在一條增廣軌 \(P\),則 \(M \oplus P\) 將會是原匹配多一的匹配,得證。
如果 \(M\) 沒有增廣軌,我們要證明它是最大匹配。
假設 \(M\) 不是最大匹配,不妨設 \(M^*\) 是一個最大匹配。
令 \(T = M \oplus M^*\),由於匹配中每個點度數不超過 \(1\),所以 \(T\) 中點度數不超過 \(2\),說明 \(T\) 是由若干個環和路徑組成的。
如果一個點有兩條鄰邊,則這兩條顯然一條屬於 \(M\),一條屬於 \(M^*\)。
如果是環,則兩個集合中的邊數量相等,由於 \(|M| < |M^*|\),說明剩下的路徑中,一定有一條路徑 \(M^*\) 中的邊更多,這說明這條路徑是一條增廣軌,和假設矛盾!所有定理得證。
尋找增廣軌
為了尋找 \(M\) 的增廣軌,我們可以先找到其所有交錯軌,進而找到增廣軌。
我們初始是一個空集 \(S\),然後對其進行擴充套件:
-
將 \(X\) 所有未匹配點全部加入 \(S\) 中。
-
重複以下操作知道不能進行:取出 \(S\) 中還未處理的 \(X\) 中的點 \(x\),找到所有 \((x,y) \in E,(x,y) \not\in M\) 的 \(y\),將 \(y\) 加入 \(S\),並將所有的 \((y,z) \in E,(y,z) \in M\) 的 \(z\) 加入 \(S\)。
如果第二步時某一時刻 \(y\) 未被匹配,說明我們找到了一條以 \(y\) 結尾的增廣軌,否則不存在。
最終演算法
我們可以不斷找到增廣軌,然後更新匹配,直到最大匹配。由於最大匹配至多為 \(n\),而找一次增廣軌按照上面的做法是 \(O(m)\) 的一次 bfs,所以總時間複雜度是 \(O(nm)\) 的。
這裡以 P3386 【模板】二分圖最大匹配 洛谷模板題為例。
點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 505;
int n, m, E;
vector<int> e[N * 2];
int mch[N * 2] = {0}, p[N * 2] = {0};
bool vis[N * 2] = {false};
bool bfs() {//找增廣軌
queue<int> q;
for (int i = 1; i <= n + m; i++) {
vis[i] = false, p[i] = 0;
if (mch[i] == 0 && i <= n)
q.push(i), vis[i] = true;
}
while (!q.empty()) {
int h = q.front();
q.pop();
for (auto i: e[h]) {
if (p[i] != 0)
continue;
p[i] = h;
if (mch[i] == 0) {
for (int j = i; j != 0;) {
mch[j] = p[j];
int nxt = mch[p[j]];
mch[p[j]] = j;
j = nxt;
}
return true;
}
if (!vis[mch[i]])
vis[mch[i]] = true, q.push(mch[i]);
}
}
return false;
}
int main() {
cin >> n >> m >> E;
for (int i = 1, u, v; i <= E; i++) {
cin >> u >> v;
e[u].push_back(v + n);
e[v + n].push_back(u);
}
while (true) {
if (!bfs())
break;
}
int ans = 0;
for (int i = 1; i <= n; i++)
ans += (mch[i] != 0);
cout << ans << endl;
return 0;
}