1. 程式人生 > 其它 >二分圖最大匹配 學習筆記

二分圖最大匹配 學習筆記

目錄

定義

二分圖

二分圖,又稱二部圖,英文名叫 Bipartite graph。
二分圖是什麼?節點由兩個集合組成,且兩個集合內部沒有邊的圖。
——OI Wiki
顯然我們發現,如果有一個二分圖,我們可以把頂點染成兩種顏色,存在一種方案使每一條邊的兩個頂點的顏色不同。
二分圖有什麼性質呢?顯然我們發現二分圖不存在長度為奇數的環。我們可以通過這一性質來檢驗一個圖是否是二分圖。

圖匹配

假設圖 \(\operatorname{G}=(\operatorname{V},\operatorname{E})\) ,其中 \(\operatorname{V}\) 是點集,\(\operatorname{E}\)

是邊集。
一組兩兩沒有公共點的邊集 \(\operatorname{M}\) 稱為這張圖的匹配
定義匹配的大小為其中邊的數量 ,其中邊數最大的為最大匹配
當圖中的邊帶權的時候,邊權和最大的為最大權匹配

  • 無法再增加邊的匹配稱為 maximal matching ,它不一定是最大匹配。
  • 匹配邊數最多的稱為 maximum matching (最大匹配)。
  • 所有點都屬於匹配稱為 perfect matching (完美匹配),同時也符合最大匹配。
  • 發生在圖的點數為奇數,剛好只有一個點不在匹配中,稱為near-perfect matching(近完美匹配),扣掉此點以後的圖稱為 factor-critical graph。

演算法解析

二分圖最大匹配,顧名思義,就是求二分圖中最大匹配的邊數。我們可以通過網路流建模來解決這個問題。
為了方便敘述,我們把這個圖分為兩個點集 \(\operatorname{A}\)\(\operatorname{B}\) ,每個點集內無邊

  1. 我們建立一個超級源和超級匯,將超級源聯向集合 \(\operatorname{A}\) 中所有點,容量為 \(1\) ,將集合 \(\operatorname{B}\) 的點全部連向超級匯,容量為 \(1\)
  2. 將圖中的邊都設為由點集 \(\operatorname{A}\) 到點集 \(\operatorname{B}\) ,容量為 \(1\)
  3. 求源點到匯點的最大流,即為答案。

演算法複雜度分析:如果使用Dinic,不難發現最多增廣 \(\sqrt{N}\) 次,每次增廣複雜度為 \(O(M)\) ,複雜度為 \(O(\sqrt{N}M)\)
其中 \(N\) 為點數 \(M\) 為邊數。
詳細證明如下( from OI Wiki

程式碼:

#include<queue>
#include<cstdio>
#include<cstring>
#define maxn 10039
#define maxm 200039
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
//#define debug
typedef int Type;
inline Type read(){
	Type sum=0;
	int flag=0;
	char c=getchar();
	while((c<'0'||c>'9')&&c!='-') c=getchar();
	if(c=='-') c=getchar(),flag=1;
	while('0'<=c&&c<='9'){
		sum=(sum<<1)+(sum<<3)+(c^48);
		c=getchar();
	}
	if(flag) return -sum;
	return sum;
}
int n,m,e,s,t,u,v,w;
int head[maxn],to[maxm],nex[maxm],c[maxm],kkk=1,now[maxn];
#define add(x,y,z) to[++kkk]=y;\
nex[kkk]=head[x];\
now[x]=head[x]=kkk;\
c[kkk]=z;
int dep[maxn];
queue<int> q,E;
int bfs(){
	memset(dep,0,sizeof(dep));
	for(int i=1;i<=n+m+2;i++) now[i]=head[i];
	q=E; q.push(s); dep[s]=1;
	while(!q.empty()){
		int cur=q.front(); q.pop();
		for(int i=head[cur];i;i=nex[i])
		    if(!dep[to[i]]&&c[i]>0){
		    	dep[to[i]]=dep[cur]+1;
		    	if(to[i]==t)
		    		return 1;
		    	q.push(to[i]);
			}
	}
	return 0;
}
int dfs(int x,int sum){
	if(x==t) return sum;
	int res=0,tmp;
	for(int i=now[x];i&&sum>0;i=nex[i]){
	    now[x]=i;
		if(dep[x]+1==dep[to[i]]&&c[i]>0){
	    	tmp=dfs( to[i],min(c[i],sum) );
	    	if(tmp==0) dep[to[i]]=0;
			c[i]-=tmp; c[i^1]+=tmp; sum-=tmp; res+=tmp;
		}
	}
	return res;
}
int ans=0;
int main(){
    //freopen("P3386_8.in","r",stdin);
    //freopen(".out","w",stdout);
    n=read(); m=read(); e=read();
    for(int i=1;i<=e;i++){
        u=read(); v=read();
    	add(u,n+v,1); add(n+v,u,0); 
	}
	s=n+m+1; t=n+m+2;
	for(int i=1;i<=n;i++){
		add(s,i,1); add(i,s,0);
	}
	for(int i=n+1;i<=n+m;i++){
		add(i,t,1); add(t,i,0);
	}
	while(bfs())
	    ans+=dfs(s,0x7fffffff);
	printf("%d",ans);
	return 0;
}