二分圖:
定義:
二分圖的定義就是:所有節點由兩個集合組成,而且兩個集合內部沒有邊的圖.
換言之,就是存在一種方案讓節點划分成滿足以上性質的兩個集合.
二分圖判定:
因為希望兩個集合內部沒有邊,所以試着用黑白兩種顏色標記圖中的節點,相鄰節點標記不同顏色,判斷是否會有沖突即可.
二分圖最大匹配
定義:
任意兩條邊沒有公共頂點的邊 的集合叫做圖的一組匹配,在二分圖中, 包含邊數 最多的一組匹配叫做二分圖的最大匹配.
解決:
我們希望匹配數量最多,所以盡量使更多的點能夠與二分圖另一半相連.
考慮這樣一種情況: \(AB,CD\) 形成一個二分圖,其中 \(A\) 連接 \(C,D\), \(B\) 連接 \(C\)
一開始,因為遍歷的順序,肯定先 \(A\) 連接 \(C\), 那么 \(B\) 怎么辦? 這就需要使用一些算法:
經常使用的是 匈牙利算法.
匈牙利算法:
過程參考了 <<算法競賽進階指南>>.
過程為:
- 設 \(S=\emptyset\), 一開始所有邊都是非匹配邊.
- 尋找增廣路,把路徑上所有邊的匹配狀態 取反 得到一個更大的匹配,重復這個操作,直到找到增廣路.
尋找增廣路:嘗試給每一個左部節點 \(x\) 找到一個匹配的右部節點 \(y\). 過程為:
- \(y\) 本身為非匹配點,直接匹配即可.
- \(y\) 本身為 \(x'\) 匹配,但從 \(x'\) 出發能找到另一個 \(y'\) 與之匹配,此時路徑的 \(x\rightarrow y\) , \(x'\rightarrow y'\) 就是一條增廣路.
增廣路過程:找 \(y\) 對應連接的 \(x'\) ,看是否能和其他點相匹配就行.
代碼:
//P3386 【模板】二分圖最大匹配
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,e;
vector<int> g[N];
int match[N],vis[N],ans;
bool dfs(int x){
for(auto y:g[x]){
if(vis[y]) continue;
vis[y]=1;
if(!match[y]||dfs(match[y])){//向下探索對應連接點是否能有增廣路
match[y]=x; return true;
}
}
return false;
}
int main(){
cin>>n>>m>>e;
for(int i=1,x,y;i<=e;i++)scanf("%d%d",&x,&y),g[x].push_back(y);
for(int i=1;i<=n;i++){memset(vis,0,sizeof(vis)); if(dfs(i)) ans++;}
cout<<ans<<endl;
system("pause");
return 0;
}
例題:[SHOI2002]舞會
題意:
給定 \(n\) 人,有 \(m\) 個限制條件要求 \(A,B\) 不能同時出現,求最多出現人的個數.
分析:
觀察數據范圍,顯然暴力的連接不連通的邊肯定過不去。因此,有一個定理:
二分圖最大獨立集 \(=\) (\(n-\) 最小點覆蓋) \(=\) (\(n-\) 二分圖最大匹配數)
我們首先把左右兩邊的限制條件建立無向邊,然后進行二分圖的染色。
這樣染色后,我們得到了顏色為 \(1\) 和顏色為 \(2\) 的點。二分圖最大匹配要求 \(1\) 和 \(2\) 顏色的點相互連接的最大個數。
利用匈牙利定理。將顏色為 \(1\) 的點跑匈牙利算法即可。得到最大匹配數量為 \(ans\),,答案即為 \(n-ans\).
代碼:
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+5;
int n,m;
vector<int> g[1005],a;
int col[N],match[N],vis[N],ans;
void getcol(int x,int c){
col[x]=c;
if(c==1) a.push_back(x);
for(auto y:g[x]){
if(!col[y]) getcol(y,3-c);
}
}
bool dfs(int x){
for(auto y:g[x]){
if(vis[y]) continue; vis[y]=1;
if(!match[y]||dfs(match[y])){
match[y]=x; return true;
}
}
return false;
}
int main(){
cin>>n>>m;
for(int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y); x++,y++;
g[x].push_back(y); g[y].push_back(x);
}
for(int i=1;i<=n;i++) if(!col[i]) getcol(i,1);
for(int i=0;i<a.size();i++){
memset(vis,0,sizeof(vis)); if(dfs(a[i])) ans++;
}
cout<<n-ans<<endl;
system("pause");
return 0;
}
[NOI2009] 變換序列
模型:
二分圖匹配有兩個要素:
- 節點能分成獨立的兩個集合,每個集合內部沒有邊
- 每個節點只能與一條匹配邊連接。
我們需要尋找題目中這種性質,就能利用二分圖匹配解決問題。
二分圖的最小點覆蓋——\(konig\) 定理
找不到那個 \(o\) 上帶兩個點的先用 \(o\) 吧...
問題:
給定一張二分圖,求出一個最小的點集 \(S\) ,使得圖中任意一條邊都有至少一個端點屬於 \(S\) 。這個問題被稱為二分圖的最小點覆蓋。
定理:
二分圖最小點覆蓋包含的點數等於二分圖最大匹配包含的邊數
最大匹配是原來二分圖邊集的一個子集,並且所有邊都不相交,所以至少需要從每條匹配邊中選出一個端點。
因此,最小點覆蓋包含的點數不可能小於最大匹配包含的邊數。
如果能對二分圖構造出一組點覆蓋,其包含的點數等於最大匹配包含的邊數,那么就能證明這個定理。
構造方法:
- 求出來二分圖最大匹配
- 從左部每個非匹配點出發,再執行一次 \(dfs\) 尋找增廣路的過程(一定會失敗),標記訪問過的節點。
- 取 左部沒有被標記的點和右部被標記的點,就得到了二分圖最小點覆蓋。
例題:
題意:
有兩台機器分別有 \(n,m\) 種模式,現在有 \(k\) 個任務,對於每個任務 \(i\) ,給定兩個整數 \(a_i,b_i\) ,如果任務在 \(A\) 執行,模式設置為 \(a_i\), \(B\) 為 \(b_i\).
需要求出來 \(a,b\) 轉換次數最小值。
分析:
二分圖最小覆蓋的模型特點則是:每條邊有兩個端點,兩者至少選擇一個端點。
在這道題中,每個任務要么在 \(A\) 上使用 \(a_i\), 要么在 \(B\) 上使用 \(b_i\)
我們可以把機器 \(A\) 的 \(m\) 種模式作為 \(m\) 個左部點,\(B\) 作為右部點。
每個任務作為無向邊,連接左部的 \(a[i]\) 個節點和右部的第 \(b[i]\) 個節點。
用盡量少的模式,處理出來所有的任務,就是 二分圖的最小覆蓋。
題意:
//UVA1194 Machine Schedule
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,k;
vector<int> g[N];
int a[N],b[N],ans,vis[N],match[N];
bool dfs(int x){
for(auto y:g[x]){
if(vis[y]) continue;
vis[y]=1;
if(!match[y]||dfs(match[y])){
match[y]=x; return true;
}
}
return false;
}
void init(){
for(int i=0;i<=1005;i++) g[i]=g[N-1];
for(int i=0;i<=1005;i++) vis[i]=match[i]=0; ans=0;
}
int main(){
while(1){
cin>>n;if(n==0) break; cin>>m>>k;
for(int i=1,t,x,y;i<=k;i++){
scanf("%d%d%d",&t,&x,&y); if(x==0||y==0) continue;
g[x].push_back(y+n); g[y+n].push_back(x);
}
for(int i=0;i<n;i++){
memset(vis,0,sizeof(vis)); if(dfs(i)) ans++;
}
printf("%d\n",ans);
init();
}
// system("pause");
return 0;
}
二分圖最大獨立集:
定義:
給定一張無向圖 \(G=(V,E)\),滿足以下條件的點集 \(S\) 叫做圖的獨立集。
- \(S\in V\)
- \((x,y)\notin E\)
定理:
二分圖的點數-二分圖最小點覆蓋=二分圖最大獨立集。
這個其實很好證明,直接用就行了。
有向無環圖的最小點覆蓋
定義:
給定一個有向無環圖,要求用盡量少的不相交的簡單路徑,覆蓋無向圖的所有頂點,這個問題被稱為無向圖的 最小路徑覆蓋
定理:
首先需要把這個有向無環圖轉化一下:把第 \(i\) 個點拆成 \(i\) 和 \(i+n\),對於每個連邊 \((x,y)\) ,連接 \((x,y+n)\),最終得到的圖叫做 拆點二分圖
最小點覆蓋包含的路徑條數,等於 \(n\) 減去拆點二分圖的最大匹配數。