1. 程式人生 > >『筆記』二分圖

『筆記』二分圖

# **定義** 如果一張無向圖的 $N$ 個節點( $N \geq 2$ ),可以分成 $U$ , $V$ 兩個非空集合,其中 $U \cap V = \Phi$ ,並且在同一集合內的點之間都沒有邊相連,那麼稱這張無向圖為一張**二分圖**。 $U$ , $V$ 分別為二分圖的左部和右部。 頂點集 $U$ , $V$ 被稱為是圖的兩個部分。 等價的 , 二分圖 可以被定義成 **圖中所有的環都有偶數個頂點**。 ![圖片來自@Lucky Block](https://i.loli.net/2021/03/06/t17PTrLv9hznwmk.png) # **二分圖判定** > 一張無向圖是二分圖,當且僅當圖中不存在**奇環**。 ## **證明** 如果某個圖是二分圖,那麼它至少有兩個結點,且所有迴路的長度均為**偶數**,任何無迴路的圖均是二分圖。 一旦新增一條邊後圖中出現了**迴路**,且**長度一定為奇數**,則該圖就不再是二分圖。 ## **思想** 根據上述定理,可以用**染色法**進行二分圖判定。 用黑白兩種顏色標記圖中的節點,當一個節點被標記後,它的所有相鄰節點應該被標記成與它相反的顏色。每個點只標記一次。 若標記過程中產生衝突,則說明存在奇環。 > 二分圖染色一般基於 $DFS$ 。 時間複雜度為 $O ( N + M )$ 。 # **實現** ```cpp /* 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 ![圖片來自@Lucky Block](https://i.loli.net/2021/03/06/Xs3N6HwaqyvfDVU.png) 上圖中的選擇方案即為原圖的一種匹配。 # **二分圖最大匹配** 雲: > 在二分圖中,包含邊數最多的一組匹配被稱為二分圖的**最大匹配**。 學長又云: > 給定一張圖 $G$ , 其中邊數最多的匹配 , 即該圖的**最大匹配**。 ![@圖片來自@Lucky Block](https://i.loli.net/2021/03/06/37J1mUbeVIBuaTx.png) ## **匈牙利演算法(增廣路演算法)** ### **核心** >> 對於一匹配 $M$ ,增廣路徑是指從 $M$ 中未使用的頂點開始 , 並從 $M$ 中未使用的頂點結束的交替路徑 。 >> >> 可以證明 , 一個匹配是最大匹配 , 當且僅當它沒有任何增廣路經。 > > 即尋找增廣路徑 , 它是一種用 **增廣路徑** 求 二分圖最大匹配的演算法。 1. 設 $S=\Phi$ ,即所有邊都是非匹配邊。 2. 尋找增廣路 $path$ ,把路徑上所有邊的匹配狀態取反,得到一個更大的匹配 $S'$ 。 3. 重複第 $2$ 步,知道圖中不存在增廣路。 ### **觀摩學長給出的一個樣例** ![@圖片來自@Lucky Block](https://pic.superbed.cn/item/5dc3e0d88e0e2e3ee95a90eb.png) + 對 $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$ 無匹配點 則此二分圖的一種最大匹配為 : ![圖片來自@Lucky Block](https://ae01.alicdn.com/kf/H17509784e3ee424fa9d656e2be4a4b50A.png) # **例題** ## **P3386 【模板】二分圖匹配** [P3386 【模板】二分圖匹配](P3386 【模板】二分圖匹配) + 如題,模板題是了。 ```cpp /* Name: P3386 【模板】二分圖匹配 Solution: 二分圖匹配 By Frather_ */ #include #include #include #include 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; } ``` ## **P2756 飛行員配對方案問題** [P2756 飛行員配對方案問題](https://www.luogu.com.cn/problem/P2756) + 匹配完成後輸出各點匹配物件非 0 的即可。 ```cpp /* Name: P2756 飛行員配對方案問題 Solution: 二分圖匹配 By Frather_ */ #include #include #include #include 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 百度百科