若是一張無向圖的 \(N\) 個節點( \(N \geq 2\) ),能夠分紅 \(U\) , \(V\) 兩個非空集合,其中 \(U \cap V = \Phi\) ,而且在同一集合內的點之間都沒有邊相連,那麼稱這張無向圖爲一張二分圖。ios
\(U\) , \(V\) 分別爲二分圖的左部和右部。算法
頂點集 \(U\) , \(V\) 被稱爲是圖的兩個部分。函數
等價的 , 二分圖 能夠被定義成 圖中全部的環都有偶數個頂點。ui
一張無向圖是二分圖,當且僅當圖中不存在奇環。spa
若是某個圖是二分圖,那麼它至少有兩個結點,且全部迴路的長度均爲偶數,任何無迴路的圖均是二分圖。code
一旦添加一條邊後圖中出現了迴路,且長度必定爲奇數,則該圖就再也不是二分圖。對象
根據上述定理,能夠用染色法進行二分圖斷定。blog
用黑白兩種顏色標記圖中的節點,當一個節點被標記後,它的全部相鄰節點應該被標記成與它相反的顏色。每一個點只標記一次。圖片
若標記過程當中產生衝突,則說明存在奇環。get
二分圖染色通常基於 \(DFS\) 。
時間複雜度爲 \(O ( N + M )\) 。
/* By 《算法競賽進階指南》 */ void dfs(int x, int col) { 賦值 v[x] ← col; 對於與 x 相連的每條無向邊(x, y) if (v[y] = 0) dfs(y, 3 - col) else if (v[y] = col) { 不是二分圖; return; } } 主函數中 { for (i = 1 → N) if (v[i] = 0) dfs(i, 1); 斷定無向圖是二分圖; }
雲:
「任意兩條邊都沒有公共端點」的邊的集合被稱爲圖的一組。
學長雲:
給定一張圖 \(G\) , 在 \(G\) 的一子圖 \(M\) 中 , \(M\) 的邊集中的任意兩條邊都沒有共同的端點 , 則稱 \(M\) 是一個匹配。
by @Lucky Block
上圖中的選擇方案即爲原圖的一種匹配。
雲:
在二分圖中,包含邊數最多的一組匹配被稱爲二分圖的最大匹配。
學長又云:
給定一張圖 \(G\) , 其中邊數最多的匹配 , 即該圖的最大匹配。
對於一匹配 \(M\) ,增廣路徑是指從 \(M\) 中未使用的頂點開始 , 並從 \(M\) 中未使用的頂點結束的交替路徑 。
能夠證實 , 一個匹配是最大匹配 , 當且僅當它沒有任何增廣路經。
即尋找增廣路徑 , 它是一種用 增廣路徑 求 二分圖最大匹配的算法。
設 \(S=\Phi\) ,即全部邊都是非匹配邊。
尋找增廣路 \(path\) ,把路徑上全部邊的匹配狀態取反,獲得一個更大的匹配 \(S'\) 。
重複第 \(2\) 步,知道圖中不存在增廣路。
對 \(Yugari\) 進行匹配 :
其直接鏈接點 \(Reimu\) 未被匹配 , 則將 \(Yugari\) 與 \(Reimu\) 進行匹配
對 \(Marisa\) 進行匹配 :
其直接鏈接點 \(Patchouli\) 未被匹配 , 則將 \(Marisa\) 與 \(Patchouli\) 進行匹配
對 \(Suika\) 進行匹配 :
其直接鏈接點 \(Reimu\) 被匹配 , 檢查 \(Reimu\) 的匹配點 \(Yugari\) 可否尋找到其餘匹配點
\(Yugari\) 可與 \(Yuyuko\) 進行匹配 , 則將 \(Yugari\) 與 \(Yuyuko\) 進行匹配
因爲\(Yugari\) 匹配對象改變 , \(Reimu\) 未被匹配 , 則將 \(Suika\) 與 \(Reimu\) 進行匹配
對 \(Aya\) 進行匹配 :
其直接鏈接點 \(Reimu\) 被匹配 , 檢查 \(Reimu\) 的匹配點 \(Suika\) 可否尋找到其餘匹配點
\(Suika\) 無其餘匹配點 , 不可將 \(Suika\) 與其餘結點進行匹配
因爲 \(Suika\) 匹配對象不可改變 , \(Reimu\) 被匹配 , 則 \(Aya\) 無匹配點
則此二分圖的一種最大匹配爲 :
[P3386 【模板】二分圖匹配](P3386 【模板】二分圖匹配)
/* Name: P3386 【模板】二分圖匹配 Solution: 二分圖匹配 By Frather_ */ #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; /*=========================================快讀*/ int read() { int x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = (x << 3) + (x << 1) + (c ^ 48); c = getchar(); } return x * f; } /*=====================================定義變量*/ int n, m, t; const int _ = 10010; struct edge { int from; int to; int nxt; } e[_]; int cnt, head[_]; bool vis[_]; int mc[_]; int ans; /*===================================自定義函數*/ void add(int from, int to) { e[++cnt].from = from; e[cnt].to = to; e[cnt].nxt = head[from]; head[from] = cnt; } bool check(int u_) { for (int i = head[u_]; i; i = e[i].nxt) if (!vis[e[i].to]) { vis[e[i].to] = true; if (!mc[e[i].to] || check(mc[e[i].to])) { mc[e[i].to] = u_; return true; } } return false; } /*=======================================主函數*/ int main() { n = read(); m = read(); t = read(); for (int i = 1; i <= t; i++) { int u = read(); int v = read(); add(u, v); } for (int i = 1; i <= n; i++) { memset(vis, false, sizeof(vis)); if (check(i)) ans++; } printf("%d\n", ans); return 0; }
/* Name: P2756 飛行員配對方案問題 Solution: 二分圖匹配 By Frather_ */ #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; /*=========================================快讀*/ int read() { int x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = (x << 3) + (x << 1) + (c ^ 48); c = getchar(); } return x * f; } /*=====================================定義變量*/ int n, m, t; const int _ = 10010; struct edge { int from; int to; int nxt; } e[_]; int cnt, head[_]; bool vis[_]; int mc[_]; int ans; /*===================================自定義函數*/ void add(int from, int to) { e[++cnt].from = from; e[cnt].to = to; e[cnt].nxt = head[from]; head[from] = cnt; } bool check(int u_) { for (int i = head[u_]; i; i = e[i].nxt) if (!vis[e[i].to]) { vis[e[i].to] = true; if (!mc[e[i].to] || check(mc[e[i].to])) { mc[e[i].to] = u_; return true; } } return false; } /*=======================================主函數*/ int main() { n = read(); m = read(); while (1) { int u = read(); int v = read(); if (u == -1 || v == -1) break; add(u, v); } for (int i = 1; i <= n; i++) { memset(vis, false, sizeof(vis)); if (check(i)) ans++; } printf("%d\n", ans); for (int i = n + 1; i <= m; i++) if (mc[i]) printf("%d %d\n", mc[i], i); return 0; }
本文圖片均來自 @Lucky Block。
----至已逝去的不是學長大大(((不是
鳴謝:
《算法競賽進階指南》
@Lucky Block
OI-wiki
百度百科
Luogu