二分图的最大匹配
来源:互联网 发布:白鲨外设淘宝店 编辑:程序博客网 时间:2024/05/19 04:07
二分图:图G中顶点集V可以分成互不相交的子集(X,Y),并且图中的每一条边所关联的点分别属于两个不同的顶点集,则图G叫二分图,如图所示:
二分图的匹配:给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配,当匹配数达到最大时为二分图的最大匹配。
当然最大匹配的方案可能不能,但是最终结果都是一样。上图中最大匹配可以由(1,1),(2,2),(3,3),(4,4)构成,也可以是:(1,1),(2,2),(3,3),(5,4)。
上图中的最大匹配数为4,这是其中的一种情况:(1,1),(2,2),(3,3),(4,4)。
增广路:若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。
匈牙利算法(转自:飘过的小牛的博客):
算法的核心是:不断寻找增广路,直到找不到增广路为止。
(图1) (图2)
图1、给出两个匹配(1,5),(2,6)。图2为在这个基础上找到一条增广路:3->6->2->5->1->4。
增广路的性质:
(1)有奇数条边。
(2)起点在二分图的左半边,终点在右半边。
(3)路径上的点一定是一个在左半边,一个在右半边,交替出现。
(4)整条路径上没有重复的点。
(5)起点和终点都是目前还没有配对的点,而其它所有点都是已经配好对的。(如图1、图2所示,(1,5)和(2,6)在图1中是两对已经配好对的点;而起点3和终点4目前还没有与其它点配对.)
(6)路径上的所有第奇数条边都不在原匹配中,所有第偶数条边都出现在原匹配中。(如图1、图2所示,原有的匹配是(1,5)和(2,6),这两条配匹的边在图2给出的增广路径中分边是第2和第4条边。而增广路径的第1、3、5条边都没有出现在图1给出的匹配中。)
(7)把增广路径上的所有第奇数条边加入到原匹配中去,并把增广路径中的所有第偶数条边从原匹配中删除(这个操作称为增广路径的取反),则新的匹配数就比原匹配数增加了1个。(如图2所示,新的匹配就是所有蓝色的边,而所有红色的边则从原匹配中删除。则新的匹配数为3。)
========================================================================================
匈牙利算法的步骤:
⑴置二分图的最大匹配M为空;
⑵找出一条增广路径P,通过异或操作获得更大的匹配M’代替M;
⑶重复⑵操作直到找不出增广路径为止。
============================================================================
所以上图中寻找最大匹配过程可能如下:
(1)在最初始时,还没有任何匹配时,图1中的两条灰色的边本身也是增广路径。
(2)找到增广路径1->5,把它取反,则匹配数增加到1。
(3)找到增广路径2->6,把它取反,则匹配数增加到2。
(4)找到增广路径3->6->2->5->1->4,把它取反,则匹配数增加到3。
(5)再也找不到增广路径,结束。
要完成匈牙利算法,还需要一个重要的定理:
如果从一个点A出发,没有找到增广路径,那么无论再从别的点出发找到多少增广路径来改变现在的匹配,从A出发都永远找不到增广路径。
算法流程:
初始化二分图的最大匹配为空for 二分图左半部分的每个点i if(从点i出发如果能找到增广路) 取反,即最大匹配+1
(1)、邻接矩阵,数据量大一点可能会超时,其中n,m为二分图两个集合的顶点编号,具体模板:
int Path(int u) //Path寻找增广路{ for(int i=1;i<=m;i++) //map记录两个顶点是否相连,即邻接矩阵,相连为1,否则为0 if(!used[i]&&map[u][i]) //如果这个点没有被访问,且与左半部分的点u相连 { used[i]=1; //标记这个点已经被访问 if(Link[i]<0||Path(Link[i])) //如果点i没有匹配,或i已经匹配,但是从Link[i]找到新的匹配 { Link[i]=u; //i和u匹配 return 1; //找到增广路 } } return 0; }int Max() //二分图的最大匹配数{ int sum=0; //初始化最大匹配为空 CLR(Link,-1); //link[x]记录当前与y节点相连的x的节点。 for(int i=1;i<=n;i++) //枚举二分图左半部分的每个点 { CLR(used,0); if(Path(i)) sum++; //能够找到增广路,则取反,匹配数+1 } return sum;}
(2)、邻接表+二分图的最大匹配模板:
struct ArcNode{ void Add(int u,int v) { next[num]=prior[u]; data[num]=v; prior[u]=num++; } void Init() { CLR(prior,-1);num=0;} int prior[MAX],next[MAX2],data[MAX2],num; }A;int Path(int u){ for(int i=A.prior[u];i!=-1;i=A.next[i]) if(!used[A.data[i]]) { used[A.data[i]]=1; if(Link[A.data[i]]<0||Path(Link[A.data[i]])) { Link[A.data[i]]=u; return 1; } } return 0; }int Max(){ int sum=0; CLR(Link,-1); for(int i=1;i<=n;i++) { CLR(used,0); if(Path(i)) sum++; } return sum;}
例题1:NYOJ 239(月老的难题),一看就知道二分图匹配问题,用邻接矩阵超时N次。
#include<iostream>#include<cstring>#include<cstdio>using namespace std;const int MAX=501;const int MAX2=10010;#define CLR(arr,val) memset(arr,val,sizeof(arr))int n,used[MAX],Link[MAX];struct ArcNode{ void Add(int u,int v) { next[num]=prior[u]; data[num]=v; prior[u]=num++; } void Init() { CLR(prior,-1);num=0;} int prior[MAX],next[MAX2],data[MAX2],num; }A;int Path(int u){ for(int i=A.prior[u];i!=-1;i=A.next[i]) if(!used[A.data[i]]) { used[A.data[i]]=1; if(Link[A.data[i]]<0||Path(Link[A.data[i]])) { Link[A.data[i]]=u; return 1; } } return 0; }int Max(){ int sum=0; CLR(Link,-1); for(int i=1;i<=n;i++) { CLR(used,0); if(Path(i)) sum++; } return sum;}int main(){ int m,u,v,Case; scanf("%d",&Case); while(Case--) { scanf("%d%d",&n,&m); A.Init(); for(int i=0;i<m;i++) { scanf("%d%d",&u,&v); A.Add(u,v); } printf("%d\n",Max()); } return 0;}
题2:Tyvj 1035(棋盘分割),二分图的最大匹配。这个关键是建图。可以把棋盘染成两个部分,黑白相间,用1表示黑,0表示白,如图当棋盘大小为4*4的矩阵时:
用map[][]保存这个图形,这个时候我们可以将格子标号为1的作为一个集合,为0的做一个集合,这样就形成了一个二分图,那么我们只要求这个二分图的最大匹配即可。由于是用一个1*2的多米诺骨牌相覆盖,那么需要满足两个格子之间要相邻,且格子不能超出范围,另外题目给了你一些限制条件要排除被挖去的格子,难点就是怎么判断两个格子相邻,如果相邻那么就构成了二分图的一条边。我们可以将每个格子编上号(编号为:i*n+j---i为行j为列,如上图第一个格子编号为0,水平过来为1,2,3,第二行为4,5,6,7....最后一个格子编号为3*4+3=15),所以这个时候可以建图为:
for(int i=0;i<n;i++) for(int j=0;j<n;j++) for(int k=0;k<4;k++) { int x=i+dx[k]; int y=j+dy[k]; if(Inside(x,y)&&map[i][j]+map[x][y]==1) A.Add(i*n+j,x*n+y); //表示i*n+j这个格子和x*n+y这个格子相连 }
对了,这种做法要用邻接表优化,否则会超内存,其余的就是模板了。
#include<iostream> #include<cstring> #include<cstdio> using namespace std; const int N=110; const int MAX=100001; #define CLR(arr,val) memset(arr,val,sizeof(arr)) int n,m,map[N][N],used[MAX],Link[MAX]; int dx[4]={0,1,0,-1},dy[4]={-1,0,1,0}; bool Inside(int x,int y) { return x>=0&&x<n&&y>=0&&y<n; } struct ArcNode{ void Add(int u,int v) { next[num]=prior[u]; data[num]=v; prior[u]=num++; } void Init() { CLR(prior,-1);num=0;} int prior[MAX],next[MAX],data[MAX],num; }A; int Path(int u) { for(int i=A.prior[u];i!=-1;i=A.next[i]) if(!used[A.data[i]]) { used[A.data[i]]=1; if(Link[A.data[i]]<0||Path(Link[A.data[i]])) { Link[A.data[i]]=u; return 1; } } return 0; } int Max() { int sum=0; CLR(Link,-1); for(int i=0;i<n*n;i++) { CLR(used,0); if(Path(i)) sum++; } return sum; } int main() { int u,v; A.Init(); scanf("%d%d",&n,&m); map[0][0]=1; for(int i=1;i<n;i++) map[0][i]=!map[0][i-1]; for(int i=1;i<n;i++) for(int j=0;j<n;j++) map[i][j]=!map[i-1][j]; for(int i=0;i<m;i++) { scanf("%d%d",&u,&v); map[u-1][v-1]=-1; } for(int i=0;i<n;i++) for(int j=0;j<n;j++) for(int k=0;k<4;k++) { int x=i+dx[k]; int y=j+dy[k]; if(Inside(x,y)&&map[i][j]+map[x][y]==1) A.Add(i*n+j,x*n+y); } printf("%d\n",Max()/2); return 0; }
1、最小顶点覆盖:
Konig定理:最小顶点覆盖=二分图的最大匹配。
POJ 1325(机器调度),记住起始点是从0开始的,在0状态下完成的不能算入到切换次数,即构图时:u*v!=0。
#include<iostream>#include<cstring>#include<cstdio>using namespace std;const int MAX=110;#define CLR(arr,val) memset(arr,val,sizeof(arr))int n,m,map[MAX][MAX],used[MAX],Link[MAX]; int Path(int u){ for(int i=1;i<m;i++) if(!used[i]&&map[u][i]) { used[i]=1; if(Link[i]<0||Path(Link[i])) { Link[i]=u; return 1; } } return 0; }int Max(){ int sum=0; CLR(Link,-1); for(int i=1;i<n;i++) { CLR(used,0); if(Path(i)) sum++; } return sum;}int main(){ int u,v,num,flag; while(scanf("%d",&n),n) { scanf("%d%d",&m,&num); CLR(map,0); for(int i=0;i<num;i++) { scanf("%d%d%d",&flag,&u,&v); if(u*v) map[u][v]=1; } printf("%d\n",Max()); } return 0;}
2、有向无环图的最小路径覆盖=顶点数-最大匹配。
POJ 2060(Taxi Cab Scheme)
#include<iostream>#include<cstring>#include<cstdio>#include<cstdlib>using namespace std;const int MAX=510;#define CLR(arr,val) memset(arr,val,sizeof(arr))int n,map[MAX][MAX],Link[MAX],used[MAX];struct Point{ int x,y,a,b; int Start; int End;}T[MAX];int Path(int u) { for(int i=1;i<=n;i++) if(!used[i]&&map[u][i]) { used[i]=1; if(Link[i]<0||Path(Link[i])) { Link[i]=u; return 1; } } return 0; }int Max(){ int sum=0; CLR(Link,-1); for(int i=1;i<=n;i++) { CLR(used,0); if(Path(i)) sum++; } return sum;} void Graph(){ for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(T[i].End+abs(T[i].a-T[j].x)+abs(T[i].b-T[j].y)<T[j].Start) map[i][j]=1;}int main(){ int Case; scanf("%d",&Case); while(Case--) { scanf("%d",&n); char time[10]; for(int i=1;i<=n;i++) { scanf("%s%d%d%d%d",time,&T[i].x,&T[i].y,&T[i].a,&T[i].b); int t1,t2; sscanf(time,"%d:%d",&t1,&t2); T[i].Start=t1*60+t2; T[i].End=T[i].Start+abs(T[i].x-T[i].a)+abs(T[i].y-T[i].b); } CLR(map,0); Graph(); cout<<n-Max()<<endl; } return 0;}
3、二分图的最大独立集=顶点数-最大匹配。(独立集:图中任意两个顶点都不相连的顶点集合)
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- 二分图的最大匹配
- MFC----MessageBox的使用
- android 基础学习(3)-----activity的生命周期
- MyEclipse 8.6 出现"Caused by: java.lang.OutOfMemoryError: PermGen space"解决
- Kconfig和Makefile
- easyUI在可编辑的datagrid中计算两列的值
- 二分图的最大匹配
- Microsoft Sync Framework Runtime 简介(一篇非常好的MSF介绍文章)
- 21.c++-cmd命令中的-argc argv
- MFC 多文档标题修改
- Tomcat 启动内存参数设置
- MFC----文件改名CFile---rename
- POJ 2513 trie+并查集+欧拉路
- 设计模式(6)-适配器模式(Apater)
- Java 复习