二分图的最大匹配
2012-08-11 09:49
183 查看
二分图:图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出发都永远找不到增广路径。
算法流程:
(1)、邻接矩阵,数据量大一点可能会超时,其中n,m为二分图两个集合的顶点编号,具体模板:
(2)、邻接表+二分图的最大匹配模板:
例题1:NYOJ 239(月老的难题),一看就知道二分图匹配问题,用邻接矩阵超时N次。
题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),所以这个时候可以建图为:
对了,这种做法要用邻接表优化,否则会超内存,其余的就是模板了。
1、最小顶点覆盖:
Konig定理:最小顶点覆盖=二分图的最大匹配。
POJ 1325(机器调度),记住起始点是从0开始的,在0状态下完成的不能算入到切换次数,即构图时:u*v!=0。
2、有向无环图的最小路径覆盖=顶点数-最大匹配。
POJ 2060(Taxi Cab Scheme)
3、二分图的最大独立集=顶点数-最大匹配。(独立集:图中任意两个顶点都不相连的顶点集合)
二分图的匹配:给定一个二分图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 ,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、二分图的最大独立集=顶点数-最大匹配。(独立集:图中任意两个顶点都不相连的顶点集合)
相关文章推荐
- 二分图的最大匹配--匈牙利算法
- zzulioj1918(二分图最大匹配)
- [置顶] 二分图最大匹配 -- 匈牙利算法
- HDU 5093 Battle ships(二分图最大匹配)
- poj 3894 System Engineer (二分图最大匹配--匈牙利算法)
- 二分图-最大匹配,最小路径覆盖,最小点覆盖(KM算法)
- [Sdoi2017]新生舞会 [01分数规划 二分图最大权匹配]
- POJ 1274 The Perfect Stall (二分图最大匹配入门题,匈牙利算法)
- [UOJ] #78. 二分图最大匹配
- hdu 5093 Battle ships(二分图最大匹配)
- poj 1274(二分图最大匹配)
- bzoj 1191 [HNOI]超级英雄Hero 二分图最大匹配(匈牙利算法)
- HDU 2063 过山车 【二分图最大匹配(匈牙利模板)】
- HDU2444 二分图判断(BFS 的染色法) + 求最大匹配边数(DFS 的匈牙利算法)
- 二分图最大匹配
- POJ 2584 T-Shirt Gumbo(二分图最大匹配)
- lightoj1149 - Factors and Multiples【二分图最大匹配】
- 学习匈牙利算法总结(求解二分图最大匹配)
- 二分图的最大匹配(匈牙利算法)
- [BZOJ]3140 二分图最大匹配