\(\text{Thanks for wikipedia}\)ios
二分圖的頂點可分紅 兩互斥的獨立集 \(U\) 和 \(V\) , 使得全部邊都連結一個 \(U\) 中的點和一個 \(V\) 中的點 .
頂點集 \(U, V\) 被稱爲是圖的兩個部分.
等價的 , 二分圖 能夠被定義成 圖中全部的環都有偶數個頂點 .git
\[\text{圖 1}\]算法
如圖 \(1\) , 可將\(U\) 和\(V\) 當作一個着色 :
\(U\) 中全部頂點爲藍色 , \(V\) 中全部頂點着綠色 , 每條邊的兩個端點的顏色不一樣, 符合圖着色問題的要求 .網絡
\[\text{圖 2}\]ui
相反的 , 非二分圖沒法被二着色 ,
如圖 \(2\) , 將其中一頂點着藍色 且另外一着綠色後 , 第三個頂點 與上述具備兩顏色的頂點相連 , 沒法再對它着藍色或綠色 .spa
給定一張圖 G , 在 G的一子圖 M 中 , M 的邊集中的任意兩條邊都沒有共同的端點 , 則稱 M是一個匹配.net
對於圖 \(1\) 所示二分圖 , 圖 \(3\) 的選擇方案 , 即爲 圖 \(1\)的一匹配3d
\[\text{圖 3}\]code
給定一張圖 \(G\) , 其中邊數最多的匹配 , 即該圖的最大匹配
對於圖 \(1\) 所示二分圖 , 圖 \(4\) 的選擇方案 , 即爲 圖 \(1\)的一最大匹配
\[\text{圖 4}\]
最大匹配可能不惟一 , 但最大匹配的邊數肯定 , 且不可能超過圖中頂點 數的一半 .
根據匹配的性質 , 一匹配中 不一樣邊對應的兩對頂點 徹底不一樣 , 不然它們相鄰 , 不知足匹配的定義
最小頂點覆蓋要求用最少的點 , 讓每條邊都至少和其中一個點關聯 .
\(\text{Knoig}\) 定理 : 二分圖的最小頂點覆蓋數等於二分圖的最大匹配數 .
在 \(n\) 個點的圖 \(G\) 中選出 \(m\) 個點 , 使這 \(m\) 個點兩兩之間沒有連邊 , 求\(m\) 最大值 .
引理 : 二分圖的最大獨立集數 = 節點數(\(n\)) — 最大匹配數(\(m\))
模板題 : https://vjudge.net/problem/HihoCoder-1121
經過 二分圖可被二着色的性質可得 ,
一個無向圖是否爲 二分圖 , 可經過 \(dfs / bfs\) 染色 , 來進行判斷 .
對於圖中每一聯通塊 , 任選其中一點 , 將其染爲綠色
顯然 , 爲知足每一條邊兩端點顏色都不一樣 , 與選擇點相連的點 都應被染爲藍色
則可以使用 \(bfs\)進行模擬染色 :
\(dfs\) 與 \(bfs\) 的複雜度 都爲\(O(n)\) , 爲避免出現遞歸爆棧狀況 , 通常使用 \(bfs\)
#include <iostream> #include <cstdio> #include <cstring> #include <vector> using namespace std; const int MAX_V = 205; vector<int> G[MAX_V]; // 圖 int V; // 頂點數 int color[MAX_V]; // 頂點i的顏色 1 或 -1 // 把頂點染成1或-1 bool dfs(int v, int c) { color[v] = c; // 把頂點v染成顏色c for (int i = 0; i < G[v].size(); ++i) { if (color[G[v][i]] == c) return false; if (color[G[v][i]] == 0 && !dfs(G[v][i], -c)) return false; } return true; } void solve() { for (int i = 0; i < V; ++i) { if (color[i] == 0) { if (!dfs(i, 1)) { puts("Wrong\n"); return ; } } } puts("Correct\n"); } int main() { int E; while (scanf("%d%d", &V, &E) == 2) { int a, b; for (int i = 0; i < V; ++i) G[i].clear(); memset(color, 0, sizeof color); for (int i = 0; i < E; ++i) { scanf("%d%d", &a, &b); G[a].push_back(b); G[b].push_back(a); } solve(); } return 0; }
//By:Luckyblock #include <cstdio> #include <cstring> #include <queue> #include <ctype.h> const int MARX = 1e4 + 10; //============================================================= struct edge { int u, v, ne; }e[MARX << 3]; int num, T, N, M, head[MARX], color[MARX]; //============================================================= inline int read() { int s = 1, w = 0; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') s = -1; for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) + (ch ^ '0'); return s * w; } inline void add(int u, int v) { e[++ num].v = v, e[num].u = u; e[num].ne = head[u], head[u] = num; } bool bfs() { for(int i = 1; i <= N; i ++) if(head[i] && color[i] == 0)//找到未被染色的 聯通塊 { std :: queue <int> q; while (!q.empty()) q.pop(); q.push(i); color[i] = 1;//對起點 進行染色 while(! q.empty()) { int u = q.front(); q.pop(); for(int j = head[u]; j; j = e[j].ne) { int v = e[j].v; if(color[v] == color[u]) return 0;//一邊兩端點 顏色相同 , 則不合法 if(! color[v]) color[v] = - color[u], q.push(v);// 染爲相反色 } } } return 1; } //============================================================= signed main() { T = read(); while(T --) { num = 0; memset(head, 0, sizeof(head)); memset(color, 0, sizeof(color)); N = read(), M = read(); bool flag = 1; for(int i = 1; i <= M; i ++) { int u = read(), v = read(); add(u, v), add(v, u); } if(bfs()) printf("Correct\n"); else printf("Wrong\n"); } }
算法核心
對於一匹配 \(M\) ,
增廣路徑是指從 \(M\) 中未使用的頂點開始 , 並從 \(M\) 中未使用的頂點結束的交替路徑 .
能夠證實 , 一個匹配是最大匹配 , 當且僅當它沒有任何增廣路經
匈牙利算法的核心 即尋找增廣路徑 , 它是一種用增廣路徑 求 二分圖最大匹配的算法
算法流程 :
先將給定二分圖分爲 \(U\) , \(V\) 兩獨立集 , 枚舉 \(U\) 中結點
對當前枚舉\(U\) 中結點 \(u\), 枚舉其相鄰 的\(V\) 中結點 \(v\)不然 , 枚舉 \(v\) 相鄰的 \(U\)中結點 , 遞歸檢查 \(v\) 點的匹配點 是否可與其餘 \(V\)中結點 配對 .
若能夠 , 則 \(u\) 可與 \(v\) 點進行匹配 , 加入最大匹配中
不然 , \(u\) 不可與 \(v\) 匹配 , \(u\) 點無匹配點 , 不可加入最大匹配中
手動模擬 :
寫的太垃圾看不懂 ? 手玩一組數據試試 :
\[\text{圖 5}\]
對 Yugari 進行匹配 :
其直接鏈接點 Reimu 未被匹配 , 則將 Yugari 與 Reimu 進行匹配
對 Marisa 進行匹配 :
其直接鏈接點 Patchouli 未被匹配 , 則將 Marisa 與 Patchouli 進行匹配
對 Aya 進行匹配 :
其直接鏈接點 Reimu 被匹配 , 檢查 Reimu 的匹配點 Suika 可否尋找到其餘匹配點因爲Suika 匹配對象不可改變 , Reimu 被匹配 , 則 Aya 無匹配點
則此二分圖的一種最大匹配爲 :
\[\text{圖 6}\]
例題 :
模板題
//By:Luckyblock #include <cstdio> #include <cstring> #include <ctype.h> #define ll long long const int MARX = 1e3 + 10; const int MARX1 = 1e7 + 10; //============================================================= struct edge { int u, v, ne; }e[MARX1]; int N, M, E, num, ans, head[MARX], match[MARX]; bool vis[MARX]; //============================================================= inline int read() { int s = 1, w = 0; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') s = -1; for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) + (ch ^ '0'); return s * w; } void add(int u, int v) { e[++ num].u = u, e[num].v = v; e[num].ne = head[u], head[u] = num; } bool dfs(int u)//匈牙利算法配對 { for(int i = head[u]; i; i = e[i].ne)//枚舉能到達的點 if(! vis[e[i].v])//在此輪配對中未被訪問 { vis[e[i].v] = 1; if(! match[e[i].v] || dfs(match[e[i].v]))//若可配對 { match[e[i].v] = u;//更新 return 1; } } return 0; } //============================================================= signed main() { N = read(), M = read(), E = read(); for(int i = 1; i <= E; i ++) { int u = read(), v = read(); if(u > N || v > M) continue; add(u, v); } for(int i = 1; i <= N; i ++)//枚舉一組中的N個點 { memset(vis, 0, sizeof(vis));//初始化 if(dfs(i)) ans ++;//能夠將i點 加入匹配中 } printf("%d", ans); }
二分圖匹配 模板 , 使用匈牙利算法
題目要求輸出方案
顯然 , 沒有匹配對象的點 , 其匹配對象爲 0
將匹配完成後 各點的非0匹配對象輸出便可
//By:Luckyblock #include <cstdio> #include <cstring> #include <ctype.h> #define ll long long const int MARX = 1e3 + 10; const int MARX1 = 1e7 + 10; //============================================================= struct edge { int u, v, ne; }e[MARX1]; int N, M, E, num, ans, head[MARX], match[MARX]; bool vis[MARX]; //============================================================= inline int read() { int s = 1, w = 0; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') s = -1; for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) + (ch ^ '0'); return s * w; } void add(int u, int v) { e[++ num].u = u, e[num].v = v; e[num].ne = head[u], head[u] = num; } bool dfs(int u)//匈牙利算法配對 { for(int i = head[u]; i; i = e[i].ne)//枚舉能到達的點 if(! vis[e[i].v])//在此輪配對中未被訪問 { vis[e[i].v] = 1; if(! match[e[i].v] || dfs(match[e[i].v]))//若可配對 { match[e[i].v] = u; //更新 return 1; } } return 0; } //============================================================= signed main() { N = read(), M = read(); while(1) { int u = read(), v = read(); if(u == -1 && v == -1) break; add(u, v); } for(int i = 1; i <= N; i ++)//枚舉一組中的N個點 { memset(vis, 0, sizeof(vis));//初始化 if(dfs(i)) ans ++;//能夠將i點 加入匹配中 } printf("%d\n", ans); for(int i = N + 1; i <= M; i ++)//輸出方案 if(match[i]) printf("%d %d\n", match[i], i); }
P4304 [TJOI2013]攻擊裝置 ( P3355 騎士共存問題 )
題目要求 :
給定一 \(01\) 矩陣 , 可在 \(0\) 的位置 放置攻擊裝置.
攻擊裝置 \((x,y)\) 可按照 "日" 字攻擊其周圍的 \(8\) 個位置
\((x-1,y-2), (x-2,y-1), (x+1,y-2), (x+2,y-1), (x-1,y+2), (x-2,y+1), (x+1,y+2), (x+2,y+1)\)
求在裝置互不攻擊的狀況下 , 最多能夠放置多少個裝置 .
分析題意 :
若將矩陣上每格看作一結點 , 向其可攻擊位置連邊
建圖後, 則題目轉化爲 : 求給定圖的 最大獨立集
因爲題目給定了一張棋盤圖 ,
可將棋盤黑白染色, 使相鄰兩格子不一樣色 .
觀察圖形發現 , 對於每個格子, 其可以攻擊到的位置 顏色必定與其不一樣
將格點轉化爲結點後 , 按照染色的不一樣, 可將各點分爲兩點集
能夠發現 , 對於每個點集之間沒有連邊 , 即每個點集都爲一獨立集
則原棋盤圖轉化爲 一二分圖
對於一二分圖, 其最大獨立集 = 節點數 \((n)\) \(—\) 最大匹配數 \((m)\)
可以使用 求得最大匹配數 , 即得答案
#include <cstdio> #include <queue> #include <cstring> #include <vector> #include <ctype.h> #define ll long long int ex[10] = {2, -2, 2, -2, -1, 1, -1, 1};//座標變化量 int ey[10] = {1, 1, -1, -1, 2, 2, -2, -2}; const int MARX = 4e4 + 10; const int MARX1 = 3e7 + 10; //============================================================= struct edge { int u, v, ne; }e[MARX1]; int N, num, node, ans, head[MARX], color[210][210], match[MARX]; bool map[210][210], vis[MARX]; std :: vector <int> U; //============================================================= inline int read() { int s = 1, w = 0; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') s = -1; for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) + (ch ^ '0'); return s * w; } void add(int u, int v) { e[++ num].u = u, e[num].v = v; e[num].ne = head[u], head[u] = num; } bool dfs(int u)//匈牙利算法 { for(int i = head[u]; i; i = e[i].ne)//枚舉能到達的點 if(! vis[e[i].v])//在此輪配對中未被訪問 { vis[e[i].v] = 1;//標記 if(! match[e[i].v] || dfs(match[e[i].v]))//若可配對 { match[e[i].v] = u;//更新 答案 return 1; } } return 0; } //============================================================= signed main() { N = read(); for(int i = 1; i <= N; i ++) { char tmp[210]; scanf("%s", tmp + 1); for(int j = 1; j <= N; j ++) { map[i][j] = (tmp[j] == '0');// 判斷一格點是否可放置裝置 node += map[i][j]; //更新總可以使用格點數 color[i][j] = (i + j) % 2 ? 1 : -1;//對棋盤按照奇偶性 黑白染色 } } for(int i = 1; i <= N; i ++)//枚舉座標 for(int j = 1; j <= N; j ++) if(map[i][j]) for(int k = 0; k < 8; k ++)//枚舉 可攻擊的點 { if(i + ex[k] < 1 || i + ex[k] > N || j + ey[k] < 1 || j + ey[k] > N) continue; if(! map[i + ex[k]][j + ey[k]]) continue; add((i - 1) * N + j, (i + ex[k] - 1) * N + j + ey[k]);//連邊 } for(int i = 1; i <= N; i ++)//將染爲 1的點加入點集 for(int j = 1; j <= N; j ++) if(map[i][j] && color[i][j] == 1) U.push_back((i - 1) * N + j); for(int i = 0, size = U.size(); i < size; i ++)//枚舉一獨立集中的點 { memset(vis, 0, sizeof(vis)); if(dfs(U[i])) ans ++;//進行配對 } printf("%d", node - ans);//求得 最大獨立集 }
咕了咕了 要是沒退役就再來更吧 = =
關於匈牙利算法的樣例解釋 :
你知道在一張徹底圖裏摳出一張二分圖有多難嗎