昨天晚上開始看二分圖,到現在基本的東西學會了
我就寫一下我自己的理解
首先什么是二分圖
顧名思義就是能分成兩個部分的圖
要注意的是,‘分’的是點
並且這兩個集合(這里我們稱作X集合和Y集合)內部所有的點之間沒有邊相連,也就是說X集合中任何兩點之間都不會有邊相連, Y亦然
定理1:無向圖G為二分圖的一個沖要條件是 1、G中至少包含兩個頂點 2、G中所有的回路長度都必須是偶數
接下來是一些概念:
匹配:設G=<V, E>為二分圖,如果 M⊆E,並且 M 中沒有任何兩邊有公共端點,則成M為G的一個匹配。【也就是說匹配的實質是一些邊的集合。】
最大匹配:邊數最多的匹配
完備匹配與完全匹配:若 X 中所有的頂點都是匹配 M 中的端點。則稱 M 為X的完備匹配。 若M既是 X-完備匹配又是 Y-完備匹配,則稱M 為 G 的完全匹配。
最小點覆蓋:用盡可能少的點去覆蓋所有的邊【最小點覆蓋集是點的集合,其個數為最小點覆蓋數】
最大點獨立:跟網絡流中的最大點權獨立集有點類似,這里指的是最大獨立的個數
接下來是二分圖的一些性質:
設無向圖G有n個頂點,並且沒有孤立頂點,那么,
1、點覆蓋數 + 點獨立數 = n
2、最小點覆蓋數 = 二分圖的最大匹配
3、最大點獨立數 = n - 最小點覆蓋數 = n - 最大匹配
二分圖的判定:
判斷一個圖是不是二分圖有兩條1、n>= 2 2、不存在奇圈
我們可以用黑白染色的方法進行判斷
1 const int maxn = 105; 2 3 int col[maxn]; 4 5 bool is_bi(int u) { 6 for(int i = 0; i < G[u].size(); i++) { 7 int v = G[u][i]; 8 if(col[v] == col[u]) return false; 9 if(!col[v]) { 10 col[v] = 3 - col[u]; 11 if(!is_bi(v)) return false; 12 } 13 } 14 return true; 15 }
接下來介紹一下求二分圖最大匹配的匈牙利算法。
匈牙利算法的思想是這樣的:如果一個圖中存在增廣路,那么沿着這條路增廣,匹配就會加1,知道不存在增廣路為止
這里的增廣路是這么定義的:對於一個未匹配或已經匹配好一部分的G來說
在X集合中的未匹配點出發,依次經過未匹配邊匹配邊未匹配邊匹配邊……而終點落在Y中的一個未訪問點上,那么只要將該路上的匹配邊於未匹配邊調換,那么新的匹配必將比原來的匹配多1,【詳細見http://blog.csdn.net/xuguangsoft/article/details/7861988中的圖】//如果不理解可以看劉汝佳大白書,一會動手模擬一下程序即可
下面是匈牙利算法的鄰接矩陣和鄰接表程序

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 6 const int maxn = 105; 7 const int INF = 1000000000; 8 9 bool vis[maxn];//查詢右集合中的點有沒有被訪問過 10 int link[maxn];//link[i]表示右集合中的i點是由左集合中的哪個點連接的 11 int G[maxn][maxn];//鄰接矩陣 12 int x_cnt; int y_cnt;//左右集合的點的個數 13 14 bool find(int u) {//用來尋找增廣路 15 for(int i = 1; i <= y_cnt; i++) {//遍歷右集合中的每個點 16 if(!vis[i] && G[u][i]) {//沒有被訪問過並且和u點有邊相連 17 vis[i] = true;//標記該點 18 if(link[i] == -1 || find(link[i])){ //該點是增廣路的末端或者是通過這個點可以找到一條增廣路 19 link[i] = u;//更新增廣路 奇偶倒置 20 return true;//表示找到一條增廣路 21 } 22 } 23 } 24 return false;//如果查找了右集合里的所有點還沒找到通過該點出發的增廣路,該點變不存在增廣路 25 } 26 27 int solve() { 28 int num = 0; 29 memset(link, -1, sizeof(link));//初始化為-1表示 不與左集合中的任何元素有link 30 for(int i = 1; i <= x_cnt; i++) {//遍歷左集合 31 memset(vis, false, sizeof(vis));//每一次都需要清除標記 32 if(find(i)) num++;//找到一條增廣路便num++ 33 } 34 return num; 35 }

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 6 const int maxn = 33; 7 const int INF = 1000000000; 8 9 struct Node { 10 int to; 11 int next; 12 }q[MaxEdge]; 13 14 struct MaxMatch() { 15 int head[MaxEdge]; 16 int tot; 17 int vis[Y_cnt]; 18 int link[Y_cnt]; 19 20 void init(int x_cnt) { 21 this -> x_cnr = x_cnt; 22 tot = 0; 23 } 24 25 void AddEdge(int u, int v) { 26 q[tot].to = v; 27 q[tot].next = head[u]; 28 head[u] = tot ++; 29 } 30 31 bool find(int u) { 32 for(int i = head[u]; i; i = q[i].next) { 33 int v = q[i].to; 34 if(!vis[v]) { 35 vis[v] = 1; 36 if(link[v] == -1 || find(link[v])) { 37 link[v] = u; 38 return true; 39 } 40 } 41 } 42 return false; 43 } 44 45 int Match() { 46 int num = 0; 47 memset(link, -1, sizeof(link)); 48 for(int i = 0; i < x_cnt; i++) { // ±éÀú×ó¼¯ºÏ 49 memset(vis, 0, sizeof(vis)); 50 if(find(X[i])) num++; 51 } 52 return num; 53 } 54 };
可以用HDU2063熟悉模板
下面也是最重要也是最難理解的二分圖的最佳匹配
上面介紹的匈牙利算法只能求出匹配邊的條數,現在我們來加個條件:讓二分圖的每個邊上都加一個權值
現在讓你求出最大(最小)權值的匹配
這里有個常用算法--KM算法
首先要引入一個概念:可行頂標。
設頂點 Xi 的頂標為 lx[i],頂點 Yj 的頂標為 ly[j],頂點 Xi 與 Yj 之間的邊權為 w[i][j] 。在算法執行過程中的任一時刻,對於任一條邊 (i,j),lx[i]+ly[j]>=w[i,j] 始終成立。
那么Lx[i] 為i可行頂標,Ly[j]為j的可行頂標
從這個角度考慮,如果滿足lx[i]+ly[j]==w[i][j]的條件下的一個子圖中存在一個完美匹配的話,那么這個匹配就一定是原圖的最大全匹配
證明:由於該匹配的可行頂標之和等於匹配的權值之和,而由於lx[i]+ly[j]>=w[i,j]其它的所有匹配的防方案權值一定會小於頂標之和。
所以問題就轉化成了通過修改可行頂標,求得最理想的匹配。
KM算法調整的方法是: 根據最后一次不成功的尋找交錯路的 DFS,取所有 i 頂點被訪問到而 j 頂點沒被訪問到的邊 (i,j) 的 lx[i]+ly[j]-w[i][j] 的最小值 d。將交錯樹中的所有左端點的頂標減小d,右端點的頂標增加 d。
經過這樣的調整以后: 原本在導出子圖里面的邊,兩邊的頂標都變了,不等式的等號仍然成立,仍然在導出子圖里面;原本不在導出子圖里面的邊,它的左端點的頂標減小了,右端點的頂標沒有變,而且由於 d 的定義,不等式仍然成立,所以他就可能進入了導出子圖里,這樣經過不斷的調整,最后就可以找到 一個有完美匹配的導出子圖(原圖的完備匹配),也就求出了該圖的最大權匹配。
代碼是劉汝佳大白書上抄的:

1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 const int maxn = 500 + 10; 7 const int INF = 1000000000; 8 9 int W[maxn][maxn], n; 10 int Lx[maxn], Ly[maxn]; // 頂標 11 int left[maxn]; // left[i]為右邊第i個點的匹配點編號 12 bool S[maxn], T[maxn]; // S[i]和T[i]為左/右第i個點是否已標記 13 14 bool match(int i) { 15 S[i] = true; 16 for(int j = 1; j <= n; j++) if (Lx[i]+Ly[j] == W[i][j] && !T[j]){ 17 T[j] = true; 18 if (!left[j] || match(left[j])){ 19 left[j] = i; 20 return true; 21 } 22 } 23 return false; 24 } 25 26 void update() { 27 int a = INF; 28 for(int i = 1; i <= n; i++) if(S[i]) 29 for(int j = 1; j <= n; j++) if(!T[j]) 30 a = min(a, Lx[i]+Ly[j] - W[i][j]); 31 for(int i = 1; i <= n; i++) { 32 if(S[i]) Lx[i] -= a; 33 if(T[i]) Ly[i] += a; 34 } 35 } 36 37 void KM() { 38 for(int i = 1; i <= n; i++) { 39 left[i] = Lx[i] = Ly[i] = 0; 40 for(int j = 1; j <= n; j++) 41 Lx[i] = max(Lx[i], W[i][j]); 42 } 43 for(int i = 1; i <= n; i++) { 44 for(;;) { 45 for(int j = 1; j <= n; j++) S[j] = T[j] = false; 46 if(match(i)) break; else update(); 47 } 48 } 49 }

1 #include <cstdio> 2 #include <cstring> 3 #include <vector> 4 #include <algorithm> 5 using namespace std; 6 7 const int maxn = 500 + 5; // 頂點的最大數目 8 const int INF = 1000000000; 9 10 // 最大權匹配 11 struct KM { 12 int n; // 左右頂點個數 13 vector<int> G[maxn]; // 鄰接表 14 int W[maxn][maxn]; // 權值 15 int Lx[maxn], Ly[maxn]; // 頂標 16 int left[maxn]; // left[i]為右邊第i個點的匹配點編號,-1表示不存在 17 bool S[maxn], T[maxn]; // S[i]和T[i]為左/右第i個點是否已標記 18 19 void init(int n) { 20 this->n = n; 21 for(int i = 0; i < n; i++) G[i].clear(); 22 memset(W, 0, sizeof(W)); 23 } 24 25 void AddEdge(int u, int v, int w) { 26 G[u].push_back(v); 27 W[u][v] = w; 28 } 29 30 bool match(int u){ 31 S[u] = true; 32 for(int i = 0; i < G[u].size(); i++) { 33 int v = G[u][i]; 34 if (Lx[u]+Ly[v] == W[u][v] && !T[v]){ 35 T[v] = true; 36 if (left[v] == -1 || match(left[v])){ 37 left[v] = u; 38 return true; 39 } 40 } 41 } 42 return false; 43 } 44 45 void update(){ 46 int a = INF; 47 for(int u = 0; u < n; u++) if(S[u]) 48 for(int i = 0; i < G[u].size(); i++) { 49 int v = G[u][i]; 50 if(!T[v]) a = min(a, Lx[u]+Ly[v] - W[u][v]); 51 } 52 for(int i = 0; i < n; i++) { 53 if(S[i]) Lx[i] -= a; 54 if(T[i]) Ly[i] += a; 55 } 56 } 57 58 void solve() { 59 for(int i = 0; i < n; i++) { 60 Lx[i] = *max_element(W[i], W[i]+n); 61 left[i] = -1; 62 Ly[i] = 0; 63 } 64 for(int u = 0; u < n; u++) { 65 for(;;) { 66 for(int i = 0; i < n; i++) S[i] = T[i] = false; 67 if(match(u)) break; else update(); 68 } 69 } 70 } 71 };