二分图最大匹配
二分图的概念
二分图又称作二部图,是图论中的一种特殊模型。设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
就长这个样。
最大匹配与增广路的概念
给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。(源自百度-二分图匹配)
最大匹配即是选择其中边数最大的子集的图。
完全匹配,也叫做完备匹配,即某个匹配中,每个顶点都和某条边相关联。
好的,现在介绍完一个基本概念,我们就要着手解决如何求解最大匹配的问题了。
若要找出二分图中的最大匹配,最朴素的方法就是找出所有的匹配并一一比较,但这种方法的时间复杂度是边数的指数级的,不能满足数据范围较大的问题。因此,我们需要找出一个更优的方法求解。
在寻找这更优的方法前,我们需要先了解增广路的概念:增广路,也称增广轨或交错轨。若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径(举例来说,有A、B集合,增广路由A中一个点通向B中一个点,再由B中这个点通向A中一个点……交替进行)。(源自百度-增广路)
由增广路的定义我们可以推出下述三个结论:
- P的路径长度必定为奇数,第一条边和最后一条边都不属于M。
- 经过取反操作可以得到一个更大的匹配M’,边数为M的边数+1。
-
M为G的最大匹配当且仅当不存在相对于M的增广路径。
这里我们来介绍一种求二分图最大匹配的算法——匈牙利算法:
我们可以这样形象的描述匈牙利算法的原理:
我们相亲现场有五个男生和四个女生,假定一号男生喜欢(无特殊说明均视为两情相悦)一号和三号女生,二号喜欢一号,三号喜欢四号,四号喜欢三号和四号,五号喜欢三号。
那么我们模拟一下匈牙利的过程:
1、
2、
我们发现二号只能找一号,而一号还可以去找三号,所以我们让一去找三,而把一号女生给二号。
3、
三去找四
4、
我们发现4号喜欢的女生都被选走了,怎么办?解决方法是令1单身而把3号女生给4号
5、
5号也喜欢3号,所以4号男生就凉了……
总结:通过上面的例子我们发现匈牙利算法真的是瞎搞……这一秒你是人生赢家,下一秒可能就凉了。
于是我们用程序来模拟这个瞎搞的过程。
就是说如果你喜欢的女生没被选走或者选走她的人有其他选择那么她就是你的了。
代码如下:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int n,m,E,lin[100010],a[1000010],b[1000010],total,ans; int M[1010][1010]; bool find(int now) { for(int i=1;i<=m;i++) { if(!M[now][i]||b[i])continue; b[i]=1; if(!a[i]||find(a[i])) { a[i]=now; return 1; } } return 0; } int main() { scanf("%d%d%d",&n,&m,&E); for(int i=1;i<=E;i++) { int u,v; scanf("%d%d",&u,&v); M[u][v]=1; } for(int i=1;i<=n;i++) { memset(b,0,sizeof(b));//记录是不是被找过 if(find(i))ans++; } printf("%d",ans); }
例题:
1、
[ZJOI2009]假期的宿舍
需要记录每个人是不是本校生。因为本校生有特权:自己可以睡自己的床(因为外校生没有床)。
我们令人去匹配床,需要记住外校生没有床(外校生可视为回家),本校生可以睡自己的床。
在匹配过程中只要有一个没回家的人找不到床,匹配失败。
#include<iostream> #include<cstdio> #include<algorithm> #include<queue> #include<cstring> using namespace std; int t,n,lin[510],total,local[510],b[510],a[510],num,c[510]; int e[510][510]; bool find(int now) { for(int i=1;i<=n;i++) { if(b[i]||!local[i]||!e[now][i])continue; b[i]=1; if(!a[i]||find(a[i])) { a[i]=now; return 1; } } return 0; } int main() { scanf("%d",&t); while(t--) { total=0; memset(lin,0,sizeof(lin)); memset(e,0,sizeof(e)); memset(local,0,sizeof(local)); memset(b,0,sizeof(b)); memset(a,0,sizeof(a)); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&local[i]); for(int i=1;i<=n;i++) { scanf("%d",&c[i]); if(!local[i]) c[i]=1; } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) scanf("%d",&e[i][j]); if(local[i])e[i][i]=1; } int judge=0; for(int i=1;i<=n;i++) { memset(b,0,sizeof(b)); if((!local[i]||!c[i])&&!find(i)) { judge=1; printf("T_T\n"); break; } } if(!judge) printf("^_^\n"); } }
2、
飞行员配对方案问题
令外籍飞行员去匹配英国飞行员,求最大匹配数。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int m,n,lin[10010],total,ans,b[10010],a[10010]; struct cym{ int from,to,next; }e[10010]; void add(int x,int y) { e[++total].from=x; e[total].to=y; e[total].next=lin[x]; lin[x]=total; } bool find(int now) { for(int i=lin[now];i;i=e[i].next) { if(b[e[i].to])continue; b[e[i].to]=1; if(!a[e[i].to]||find(a[e[i].to])) { a[e[i].to]=now; a[now]=e[i].to; return 1; } } return 0; } int main() { scanf("%d%d",&m,&n); int x,y; while(scanf("%d%d",&x,&y)!=EOF&&x!=-1) add(x,y); for(int i=1;i<=m;i++) { memset(b,0,sizeof(b)); if(find(i))ans++; } if(!ans) printf("No Solution!"); else { printf("%d\n",ans); for(int i=1;i<=m;i++) if(a[i])printf("%d %d\n",i,a[i]); } }