【二分圖】匈牙利 & KM
【二分圖】匈牙利 & KM
二分圖
概念:
一個圖 \(G=(V,E)\) 是無向圖,如果頂點 \(V\) 可以分成兩個互不相交地子集 \(X,Y\)
且任意一條邊的兩個頂點一個在 \(X\) 中,一個在 \(Y\) 中,則稱 \(G\) 是二分圖
性質:
當且僅當無向圖 \(G\) 的所有環都是偶環時, \(G\) 才是個二分圖
判定:
可從任意一點開始 \(\text{dfs}\),按距離編號
如果要編號的點已經被編號,可根據奇偶判斷是否是二分圖
bool dfs(int u) { for (int i = lst[u], v; i; i = nxt[i]) { v = to[i]; if (cl[u] == cl[v]) return 0; if (!cl[v]) { cl[v] = 3 - cl[u]; if (!dfs(v)) return 0; } } return 1; } int main() { cl[1] = 1; dfs(1); }
二分圖的匹配
概念:
一個二分圖 \(G\) 的一個子圖 \(M\), 若在 \(M\) 中的任意兩條邊都不依附同一個頂點
則稱 \(M\) 是一個匹配
最大匹配:
即選擇邊數最大的匹配
完備匹配:
某一部的頂點全部與匹配中的某條邊關聯
完美匹配:
所有頂點都和匹配中的某條邊關聯
增廣路
定義:
\(M\) 是二分圖 \(G\) 的已匹配邊的集合,若 \(P\) 是一條聯通兩個在不同部的未匹配點的路徑,
且路徑上匹配邊與未匹配邊交替出現,則 \(P\) 是相對於 \(M\) 的增廣路。
性質:
-
第一條和最後一條都是未匹配邊,邊數為奇數
因為要聯通兩個在不同部的未匹配點
-
一個增廣路徑 \(P\)
-
\(M\) 是 \(G\) 的最大匹配當且僅當不存在相對於 \(M\) 的增廣路
最大匹配
匈牙利演算法
根據增廣路的性質,我們也可以想到一種演算法
- 清空 \(M\)
- 尋找 \(M\) 的增廣路 \(P\),通過取反得到更大的 \(M'\) 代替 \(M\)
- 重複 (2) 直到找不到增廣路為止
這就是匈牙利演算法
實現
用 \(\text{dfs}\) ,從 \(X\) 部的一個未匹配點開始,訪問鄰接點 \(v\) (一定是 \(Y\) 部的)
-
如果 \(v\) 未匹配,則已找到一條增廣路
-
否則,找出 \(v\) 的匹配頂點 \(w\)
因為要"取反",所以要使 \((w,v)\) 未匹配, \((u,v)\) 匹配。
能實現這一點就需要從 \(w\) 出發找一條新增廣路,如果行,則可以找到增廣路
實現:
// cx[i] 是 X 部的點 i 匹配的 Y 部點
// cy[i] 是 Y 部的點 i 匹配的 X 部點
bool dfs(int u) {
for (int i = 1; i <= m; i++)
if (mp[u][i] && !vis[i]) {
vis[i] = 1;
if (!cy[i] || dfs(cy[i])) {
cx[u] = i, cy[i] = u;
return 1;
}
}
return 0;
}
inline void match() {
memset(cx, 0, sizeof(cx));
memset(cy, 0, sizeof(cy));
for (int i = 1; i <= n; i++) {
memset(vis, 0, sizeof(vis));
ans += dfs(i);
}
}
時間複雜度:
- 鄰接矩陣:\(O(n^3)\)
- 前向星:\(O(nm)\)
最大匹配的性質
-
最小點覆蓋 = 最大匹配
最小點覆蓋:選擇最少的點使得每條邊都至少和其中一個點關聯
- 證明:最大匹配能保證剩下的邊都與至少一個點關聯
-
最小邊覆蓋 = 總點數 - 最大匹配
最小邊覆蓋:選擇最少的邊去覆蓋所有點
-
證明:設總點數是 \(n\),最大匹配是 \(m\)
則最大匹配能覆蓋 \(2m\) 個點,設剩下 \(a\) 個點
則這 \(a\) 個點需要單獨用 \(a\) 條邊覆蓋,最小邊覆蓋 = \(m+a\)
因為 \(2m+a=n\)
所以最小邊覆蓋 = \((2m+a)-m=n-m\)
-
-
最大點獨立集 = 總點數 - 最小點覆蓋
最大點獨立集:在二分圖中選出最多的頂點,使得任兩點之間沒有邊相連
-
證明:刪去最小點覆蓋的點集,對應的邊也沒有了,剩下的點就是獨立集
因為是最小點覆蓋,減去後就是最大點獨立集
-
最佳匹配
如果邊有權,則權和最大的匹配叫最佳匹配
有連線 \(X,Y\) 部的頂點 \(X_iY_j\) 的一條邊 \(w_{i,j}\) ,要求一種使 \(\sum w_{i,j}\) 最大的匹配
頂標:給頂點的一個標號
設 \(X_i\) 的頂標 \(A_i\) , \(Y_j\) 的頂標 \(B_j\) ,則在任意時刻需要滿足 \(A_i+B_j\ge w_{i,j}\) 成立
相等子圖:由 \(A_i+B_j=w_{i,j}\) 的邊構成的字圖
性質:如果相等字圖有完備匹配,那麼這個完備匹配是最佳匹配
KM
核心:通過修改頂標使得能找到完備匹配
-
初始化: \(A_i\) 為所有與 \(X_i\) 相連邊的最大權, \(B_i=0\)
-
尋找完備匹配失敗,得到一條路徑,叫做交錯樹
將交錯樹中 \(X\) 部的頂標全部減去 \(d\) , \(Y\) 部的頂標全部加上 \(d\) ,會發現
-
兩端都在交錯樹中的邊, \(A_i+B_j\) 不變,仍在相等字圖中
都不在,仍然不在相等字圖
-
\(X\) 不在 \(Y\) 在,\(A_i+B_j\) 增大,仍然不在相等字圖
-
\(X\) 在 \(Y\) 不在,\(A_i+B_j\) 減小,現在可能在相等字圖中,使得相等字圖擴大
為了使至少有一條邊進入相等字圖,且 \(A_i+B_j\ge w_{i,j}\) 恆成立
\(d\) 應該等於 \(\min A_i+B_j-w_{i,j}\)
-
-
重複 (2) 直到找到完備匹配
複雜度:樸素實現是 \(O(n^4)\) 的
在相等字圖上找增廣路 \(O(n^2)\) ,每次改頂標最多 \(n\) 次增廣路,要改 \(n\) 次頂標
// lx, ly :頂標
// cy[i] 是 y 部點 i 匹配的 X 部點
bool dfs(int x) {
vx[x] = 1;
for (int i = 1, cz; i <= n; i++) {
if (vy[i]) continue;
cz = lx[x] + ly[i] - mp[x][i];
if (cz == 0) {
vy[i] = 1;
if (!cy[i] || dfs(cy[i])) {
cy[i] = x;
return 1;
}
} else lack = min(lack, cz);
}
return 0;
}
inline void KM() {
memset(cy, 0, sizeof(cy));
for (int i = 1; i <= n; i++)
for (;;) {
memset(vx, 0, sizeof(vx));
memset(vy, 0, sizeof(vy));
lack = 2e9;
if (dfs(i)) break;
for (int j = 1; j <= n; j++) {
if (vx[j]) lx[j] -= lack;
if (vy[j]) ly[j] += lack;
}
}
}
\(O(n^3)\) 的做法
其實是可以實現 \(O(n^3)\) 的,
-
我們給每個 \(Y\) 頂點一個鬆弛量 \(\text{slack}\) ,初始為正無窮
-
對於每條邊 \((i, j)\), 若不在相等字圖中,\(\text{slack}_j=\min(A_i+B_j-w_{i,j})\)
-
修改頂標時 \(d\) 取所有不在交錯樹中的 \(Y\) 部點的 \(\text{slack}\) 的最小值
-
修改完後,所有不在交錯樹中的 \(Y\) 部點的 \(\text{slack}\) 都減去 \(d\)
int slk[N], pre[N], mat[N];
// mat 等同於 cy
inline void bfs(int st) {
memset(pre, 0, sizeof(pre));
for (int i = 1; i <= n; i++) slk[i] = 1e9;
int x, y = 0, del, pos;
mat[y] = st;
do {
x = mat[y], vy[y] = 1, del = 1e9;
for (int i = 1; i <= n; i++) {
if (vy[i]) continue;
if (slk[i] > lx[x] + ly[i] - mp[x][i])
slk[i] = lx[x] + ly[i] - mp[x][i], pre[i] = y;
if (slk[i] < del) del = slk[i], pos = i;
}
for (int i = 0 ; i <= n; i++) {
if (vy[i]) lx[mat[i]] -= del, ly[i] += del;
else slk[i] -= del;
}
y = pos;
} while (mat[y]);
while (y) mat[y] = mat[pre[y]], y = pre[y];
}
inline void KM() {
memset(mat, 0, sizeof(mat));
memset(lx, 0, sizeof(lx));
memset(ly, 0, sizeof(ly));
for (int i = 1; i <= n; i++) {
memset(vy, 0, sizeof(vy));
bfs(i);
}
}
答案
for (int i = 1; i <= n; i++) {
if (mp[mat[i]][i]==-1e9 || !mat[i]) {
puts("-1");
break;
}
ans += mp[mat[i]][i];
}