二分图最大匹配 学习笔记

定义

二分图

二分图,又称二部图,英文名叫 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

Update on 2021.8.17

更改了代码,(原来写假了)。
更改的地方:在 dfs 函数增加了 if(!sum) break; 这句话。
代码:

#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;
		}
            if(!sum) break;
	}
	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;
}
posted @ 2021-07-20 19:40  jiangtaizhe001  阅读(122)  评论(0编辑  收藏  举报