一、二分圖的基本概念
【二分圖】
二分圖又稱作二部圖,是圖論中的一種特殊模型。
設G=(V,E)是一個無向圖,如果頂點V可分割為兩個互不相交的子集(A,B),並且圖中的每條邊(i,j)所關聯的兩個頂點i和j分別屬於這兩個不同的頂點集(i in A,j in B),則稱圖G為一個二分圖。

也就是說,只要兩個點之間有邊,那么這兩個點就不能同屬一個集合,必須分在兩邊。
這就帶來一個問題,並不是所有的無向圖G都能轉化為二分圖。
【二分圖的判定】
定理:無向圖G為二分圖的充要條件是,G至少有兩個頂點,且其所有回路的長度均為偶數。
判定方法:根據定義比根據定理更好判定,只需要從第一個點出發,將其染成顏色1,則與其所有相連的所有終點都染成顏色2,如果與其相連的點已經被染成顏色1,則圖G不是二分圖。
把該點的所有終點當成新的起點,重復以上過程。很顯然,既可以用dfs染色,又可以用bfs染色。
dfs染色代碼:
#include<iostream> #include<vector> #include<cstring> using namespace std; const int maxn = 10010; int n,m;//頂點數 邊數 vector<int> G[maxn]; int color[maxn] ; //0 沒染色 1 -1不同色 bool dfs(int u, int c){ color[u] = c; for(int i=0; i<G[u].size(); i++){ int v = G[u][i]; if(color[v] == c) return false; if(color[v] == 0 && !dfs(v,-c)) return false; } return true; } bool solve(){ for(int i=1; i<=n; i++){ if(color[i] == 0) if(!dfs(i,1)){ return false; } } return true; } int main(){ int t; cin>>t; while(t--){ cin >> n >> m; memset(color, 0, sizeof(color)); for(int i=0; i<maxn; i++) G[i].clear(); for(int i = 0; i < m; i++) { int s, t; cin >> s >> t; G[s].push_back(t); G[t].push_back(s); // 如果有向圖則無需這一句 } if(solve()){ cout<<"Correct"<<endl; } else cout<<"Wrong"<<endl; } return 0; }
bfs染色代碼
// 使用鄰接矩陣 int G[maxn][maxn]; bool bfs(int s) { color[s] = 1; queue<int> que; que.push(s); while(!que.empty()) { int from = que.front(); que.pop(); for(int i = 1; i <= V; i++) { // 如果相鄰的點沒有上色就給這個點上色 if(G[from][i] && color[i] == 0) { que.push(i); color[i] = -color[from]; } // 如果相鄰的顏色相同則返回false if(G[from][i] && color[i] == color[from]) return false; } } // 如果所有的點都被染過色,且相鄰的點顏色都不一樣,返回true return true; }
二、二分圖最大匹配
定義:在一個無向圖中,定義一條邊覆蓋的點為這條邊的兩個端點。
找到一個邊集S包含最多的邊,使得這個邊集覆蓋到的所有頂點中的每個頂點只被一條邊覆蓋。S的大小叫做圖的最大匹配。
通俗地講,比如說有一場宴會,男孩和女孩跳舞,並且他們必須互相喜歡才能一起跳舞,一個男孩可能喜歡0個或多個女孩,一個女孩也可能喜歡0個或多個男孩,但一個男孩和他喜歡地女孩跳舞之后就不能和其他他喜歡地女孩跳舞,女孩亦是如此。請問最多可以多少對男孩女孩一起跳舞。
很顯然,互相喜歡的男孩和女孩建邊得到一個二分圖,求一個一個邊集S包含最多的邊,使得這個邊集覆蓋到的所有頂點中的每個頂點只被一條邊覆蓋。即求二分圖的最大匹配。
【匈牙利算法】
把二分圖分為A,B兩個集合,依次枚舉A中的每個點,試圖在B集合中找到一個匹配。
對於A集合中一點x,假設B集合中有一個與其相連的點y,若y暫時還沒有匹配點,那么x可以和y匹配,找到;
否則,設y已經匹配的點為z(顯然z是A集合中的一個點),那么,我們將嘗試為z找到一個除了y之外的匹配點,若找到,那么x可以和y匹配,否則x不能與y匹配。
【圖解】來自博客:https://www.cnblogs.com/jianglangcaijin/p/6035950.html
我們以下圖為例說明匈牙利匹配算法。

step1:從1開始,找到右側的點4,發現點4沒有被匹配,所以找到了1的匹配點為4 。得到如下圖:

step2:接下來,從2開始,試圖在右邊找到一個它的匹配點。我們枚舉5,發現5還沒有被匹配,於是找到了2的匹配點,為5.得到如下圖:

step3:接下來,我們找3的匹配點。我們枚舉了5,發現5已經有了匹配點2。此時,我們試圖找到2除了5以外的另一個匹配點,我們發現,我們可以找到7,於是2可以匹配7,所以5可以匹配給3,得到如下圖:

此時,結束,我們得到最大匹配為3。
【算法核心】
每次尋找可以匹配A點的一個點B,如果這個點B還沒有被匹配,暫時就把這個點B當作A的匹配點;如果這個B在之前已經匹配了C,那就看C能不能匹配除了B以外的未匹配點,如果找不到則重復以上過程直到找到或者枚舉所有可能點還找不到,結束點A的匹配;如果找到,則把B勻給A(本來B是C的,現在C說我還有其他舞伴,B就讓給A了)。這樣就能多出一個匹配,相當於找到一條“增廣路徑”。
【匈牙利算法模板】詳見博客https://www.cnblogs.com/penseur/archive/2013/06/16/3138981.html
poj 1469 COURSES
已知,有N個學生和P門課程,每個學生可以選0門,1門或者多門課程,要求在N個學生中選出P個學生使得這P個學生與P門課程一一對應。
DFS
#include<iostream> #include<queue> #include<list> #include<vector> #include<cstring> #include<set> #include<stack> #include<map> #include<cmath> #include<algorithm> #include<string> #include<stdio.h> using namespace std; typedef long long ll; #define MS(x,i) memset(x,i,sizeof(x)) #define rep(i,s,e) for(int i=s; i<=e; i++) #define sc(a) scanf("%d",&a) #define scl(a) scanf("%lld",&a) #define sc2(a,b) scanf("%d %d", &a, &b) #define debug printf("debug......\n"); #define pfd(x) printf("%d\n",x) #define pfl(x) printf("%lld\n",x) const double eps=1e-8; const double PI = acos(-1.0); const int inf = 0x3f3f3f3f; const ll INF = 0x7fffffff; const int maxn = 5e2+10; int dx[4] = {0, 0, 1, -1}; int dy[4] = {1, -1, 0 , 0}; int match[maxn];//匹配 vector<int> G[maxn]; bool vis[maxn]; int n, m; bool dfs(int u) { for(int i=0; i<G[u].size(); i++){ int v = G[u][i]; if(!vis[v]){ vis[v] = 1; if(match[v] == -1 || dfs(match[v])){ match[v] = u; return true; } } } return false; } void solve(){ int ans = 0; MS(match, -1); for(int i = 1; i <= n; i++){ MS(vis, 0); if(dfs(i)) ans++; } // printf("%s\n", ans==n ? "YES":"NO"); if( ans == n) printf("YES\n"); else printf("NO\n"); } int main() { int t; sc(t); while(t--){ int k,c; sc2(n,m); rep(i,0,n) G[i].clear(); rep(i,1,n){ sc(k); while(k--){ sc(c); G[i].push_back(c); } } solve(); } }
【Hopcroft-Karp算法】
利用匈牙利算法一次只能找到一條增廣路徑,Hopcroft-Karp就提出一次找到多條不相交的增廣路徑(不相交就是沒有公共點和公共邊的增廣路徑),然后根據這些增廣路徑添加多個匹配。

【Hopcroft-Karp算法模板】
#include<iostream> #include<queue> using namespace std; const int MAXN=500;// 最大點數 const int INF=1<<28;// 距離初始值 int bmap[MAXN][MAXN];//二分圖 int cx[MAXN];//cx[i]表示左集合i頂點所匹配的右集合的頂點序號 int cy[MAXN]; //cy[i]表示右集合i頂點所匹配的左集合的頂點序號 int nx,ny; int dx[MAXN]; int dy[MAXN]; int dis; bool bmask[MAXN]; //尋找 增廣路徑集 bool searchpath() { queue<int>Q; dis=INF; memset(dx,-1,sizeof(dx)); memset(dy,-1,sizeof(dy)); for(int i=1;i<=nx;i++) { //cx[i]表示左集合i頂點所匹配的右集合的頂點序號 if(cx[i]==-1) { //將未遍歷的節點 入隊 並初始化次節點距離為0 Q.push(i); dx[i]=0; } } //廣度搜索增廣路徑 while(!Q.empty()) { int u=Q.front(); Q.pop(); if(dx[u]>dis) break; //取右側節點 for(int v=1;v<=ny;v++) { //右側節點的增廣路徑的距離 if(bmap[u][v]&&dy[v]==-1) { dy[v]=dx[u]+1; //v對應的距離 為u對應距離加1 if(cy[v]==-1) dis=dy[v]; else { dx[cy[v]]=dy[v]+1; Q.push(cy[v]); } } } } return dis!=INF; } //尋找路徑 深度搜索 int findpath(int u) { for(int v=1;v<=ny;v++) { //如果該點沒有被遍歷過 並且距離為上一節點+1 if(!bmask[v]&&bmap[u][v]&&dy[v]==dx[u]+1) { //對該點染色 bmask[v]=1; if(cy[v]!=-1&&dy[v]==dis) { continue; } if(cy[v]==-1||findpath(cy[v])) { cy[v]=u;cx[u]=v; return 1; } } } return 0; } //得到最大匹配的數目 int MaxMatch() { int res=0; memset(cx,-1,sizeof(cx)); memset(cy,-1,sizeof(cy)); while(searchpath()) { memset(bmask,0,sizeof(bmask)); for(int i=1;i<=nx;i++) { if(cx[i]==-1) { res+=findpath(i); } } } return res; } int main() { int num; scanf("%d",&num); while(num--) { memset(bmap,0,sizeof(bmap)); scanf("%d%d",&nx,&ny); for(int i=1;i<=nx;i++) { int snum; scanf("%d",&snum); int u; for(int j=1;j<=snum;j++) { scanf("%d",&u); bmap[i][u]=1; // bmap[u][i]=1; } } // cout<<MaxMatch()<<endl; if(MaxMatch()==nx) { printf("YES\n"); } else { printf("NO\n"); } } //system("pause"); return 0; } /* 4 1 3 1 3 4 2 */
三、二分圖最小頂點覆蓋
定義:假如選了一個點就相當於覆蓋了以它為端點的所有邊。最小頂點覆蓋就是選擇最少的點來覆蓋所有的邊。
定理:最小頂點覆蓋等於二分圖的最大匹配。
四、最大獨立集
定義:選出一些頂點使得這些頂點兩兩不相鄰,則這些點構成的集合稱為獨立集。找出一個包含頂點數最多的獨立集稱為最大獨立集。
定理:最大獨立集 = 所有頂點數 - 最小頂點覆蓋 = 所有頂點數 - 最大匹配
【模板】
建邊是相反意義建邊,把有沖突關系的兩個點建邊,再套用最大匹配的模板,用所有頂點數減之即可。
【代碼】
#include <iostream> #include <cstdio> #include <cstring> #include <queue> #include <vector> #define INF 0x3f3f3f3f using namespace std; const int MAXN = 40005; int n;//頂點數 vector<int>prime; int vis[500005]; int num[MAXN],p[2][500005]; vector<int> G[MAXN],v[MAXN]; int Mx[MAXN],My[MAXN]; int dx[MAXN],dy[MAXN]; int dis; bool used[MAXN];//DFS中甬道的訪問標記 void get_prime(){ memset(vis,0,sizeof(vis)); prime.clear(); for(int i = 2; i <= 500000; i++){ int tt = 500000/i; for(int j = 2; j <= tt; j++) vis[i*j] = 1; } for(int i = 2; i <= 500000; i++){ if(!vis[i]) prime.push_back(i); } } void init(){ for(int i = 0; i <= n; i++){ G[i].clear(); v[i].clear(); } } bool SearchP(){ queue<int> q; dis = INF; memset(dx,-1,sizeof(dx)); memset(dy,-1,sizeof(dy)); for(int i = 0 ; i < n; i++){ if(Mx[i] == -1){ q.push(i); dx[i] = 0; } } while(!q.empty()){ int u = q.front(); q.pop(); if(dx[u] > dis) break; int l = G[u].size(); for(int i = 0; i < l; i++){ int v = G[u][i]; if(dy[v] == -1){ dy[v] = dx[u] + 1; if(My[v] == -1) dis = dy[v]; else{ dx[My[v]] = dy[v] + 1; q.push(My[v]); } } } } return dis != INF; } bool dfs(int u){ int l = G[u].size(); for(int i = 0; i < l; i++){ int v = G[u][i]; if(!used[v] && dy[v] == dx[u] + 1){ used[v] = true; if(My[v] != -1 && dy[v] == dis) continue; if(My[v] == -1 || dfs(My[v])){ My[v] = u; Mx[u] = v; return true; } } } return false; } int MaxMatch(){ int res = 0; memset(Mx, -1, sizeof(Mx)); memset(My, -1, sizeof(My)); while(SearchP()){ memset(used, false, sizeof(used)); for(int i = 0; i < n; i++) if(Mx[i] == -1 && dfs(i)) res++; } return res; } int main(){ get_prime(); int T,t = 1; scanf("%d",&T); while(T--){ scanf("%d",&n); init(); memset(p,0,sizeof(p)); for(int i = 1; i <= n; i++){ scanf("%d",&num[i]); int t = num[i],cnt = 0; for(int j = 0; prime[j]*prime[j] <= t; j++){ if(t % prime[j] == 0){ v[i].push_back(prime[j]); while(t%prime[j] == 0) cnt++,t /= prime[j]; } } if(t > 1){ v[i].push_back(t); cnt++; } p[cnt&1][num[i]] = i; } for(int i = 1; i <= n; i++){ if(p[0][num[i]]){ for(int j = 0; j < v[i].size(); j++){ int tmp = num[i]/v[i][j]; if(!p[1][tmp]) continue; G[i-1].push_back(p[1][tmp-1]); } } else{ for(int j = 0; j < v[i].size(); j++){ int tmp = num[i]/v[i][j]; if(!p[0][tmp]) continue; G[p[0][tmp]-1].push_back(i-1); } } } printf("Case %d: %d\n",t++,n-MaxMatch()); } return 0; }
