参考 http://blog.csdn.net/q3498233/article/details/5786225
二分图:二分图是这样一个图,它的顶点可以分类两个集合X和Y,所有的边关联的两个顶点恰好一个属于集合X,另一个属于集合Y。
二分图匹配:给定一个二分图G,在G的一个子图M中,M的边集中的任意两条边都不依附于同一个顶点,则称M是一个匹配。
最大匹配:图中包含边数最多的匹配称为图的最大匹配。
完美匹配:如果所有点都在匹配边上,则称这个最大匹配是完美匹配。
二分图匹配基本概念:
未盖点
设VI是G的一个顶点,如果VI不与任意一条属于匹配M的边相关联,就称VI是一个未盖点。
交错轨
设P是图G的一条轨,如果P的任意两条相邻的边一定是一条属于M而另一条不属于M,就称P是交错轨。
可增广轨(增广路)
两个端点都是未盖点的交错轨称为可增广轨。
可增广轨的性质:
1:P的路径长度必定为奇数,第一条边和最后一条边都不属于M。
2:P经过取反操作可以得到一个更大的匹配M’。
3:M为G的最大匹配当且仅当不存在相对于M的增广路径。
二分图最大匹配匈牙利算法:
算法的思路是不停的找增广轨,并增加匹配的个数,增广轨顾名思义是指一条可以使匹配数变多的路径,在匹配问题中,增广轨的表现形式是一条"交错轨",也就是说这条由图的边组成的路径,它的第一条边是目前还没有参与匹配的,第二条边参与了匹配,第三条边没有..最后一条边没有参与匹配,并且始点和终点还没有被选择过.这样交错进行,显然他有奇数条边.那么对于这样一条路径,我们可以将第一条边改为已匹配,第二条边改为未匹配...以此类推.也就是将所有的边进行"取反",容易发现这样修改以后,匹配仍然是合法的,但是匹配数增加了一对.另外,单独的一条连接两个未匹配点的边显然也是交错轨.可以证明,当不能再找到增广轨时,就得到了一个最大匹配.这也就是匈牙利算法的思路。
代码:
输入的时候可以1个对应多个,而寻找到的最大匹配中任意2个边是没有公共交点的
模板
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> #include<vector> using namespace std; const int MAXN=11111;//根据点的个数变 int linker[MAXN]; bool used[MAXN]; vector<int>map[MAXN]; int uN; bool dfs(int u) { for(int i=0;i<map[u].size();i++) { if(!used[map[u][i]]) { used[map[u][i]]=true; if(linker[map[u][i]]==-1||dfs(linker[map[u][i]])) { linker[map[u][i]]=u; return true; } } } return false; } int hungary() { int u; int res=0; memset(linker,-1,sizeof(linker)); for(u=0;u<uN;u++) { memset(used,false,sizeof(used)); if(dfs(u)) res++; } return res; } int main() { int u,k,v,i; int n,m; while(scanf("%d %d",&n,&m)!=EOF)//n个点 m条边 { for( i=0;i<MAXN;i++) map[i].clear(); for(i=0;i<m;i++) { scanf("%d %d",&v,&u); v=v-1;//如果点是从1开始计数的 要减1 如果从0开始去掉这句以及下面那句 u=u-1; map[u].push_back(v); map[v].push_back(u); } uN=n; printf("%d\n",hungary()/2); } return 0; }
真正求二分图的最大匹配的题目很少,往往做一些简单的变化:
变种1:二分图的最小顶点覆盖
最小顶点覆盖要求用最少的点(X或Y中都行),让每条边都至少和其中一个点关联。
knoig定理:二分图的最小顶点覆盖数 = 二分图的最大匹配数(m)。
变种2:DAG图的最小路径覆盖
用尽量少的不相交简单路径覆盖有向无环图(DAG)G的所有顶点,这就是DAG图的最小路径覆盖问题。
结论:DAG图的最小路径覆盖数 = 节点数(n)- 最大匹配数(m)
变种3:二分图的最大独立集
结论:二分图的最大独立集数 = 节点数(n)— 最大匹配数(m)
最大独立集相当于问:在N个点的图G中选出m个点,使这m个点两两之间没有边.求m最大值
当我们遇到的题目中可以将数据分成2个集合的时候就应该敏感性的想到二分匹配
最大独立集例题 :
最大独立集通常采用互斥的关系建立边
用二分匹配找出所有的互斥的关系个数 用总数n一减就是最大独立集的个数
//Cat VS Dog.cpp : 定义控制台应用程序的入口点。 // /* 题目描述:动物园有N只猫,M只狗,P个小孩。每个小孩都有自己喜欢的动物和讨厌的动物,如果他喜欢狗,那么就讨厌猫, 如果他讨厌猫,那么他就喜欢狗。某个小孩能开心,当且仅当他喜欢的动物留在动物园和讨厌的动物不在动物园里面。 现让管理员通过带走某些动物,问最多能使多少个孩子开心。 解题思路:题目有一个关键点,孩子喜欢猫,必然不喜欢狗,反之。 即猫和猫之间,狗和狗之间一定不存在矛盾关系,符合二分图的概念。 如何建立二分图: 若甲小孩喜欢的动物与乙小孩讨厌的动物一样,或者甲小孩讨厌的动物与乙小孩喜欢的动物一样,那甲乙之间就存在着排斥关系,则他们之间连接一条边。 建立完二分图之后,相当于求二分图的最大独立集 = 顶点数 - 最大匹配数。 */ #include "stdafx.h" #include <iostream> #include <cstring> #include <string> #include <algorithm> using namespace std; const int MAXN = 508; struct Child { string like; string dis; }; Child cat[MAXN],dog[MAXN]; int map[MAXN][MAXN]; int link[MAXN]; int used[MAXN]; int cat_num; int dog_num; int dfs(int k) { for(int i=0; i<dog_num; i++) { if(!used[i] && map[k][i]) { used[i] = 1; if(link[i] == -1 || dfs(link[i])) { link[i] = k; return 1; } } } return 0; } int maxMatch() { int cnt = 0; for(int i=0; i<cat_num; i++) { memset(used,0,sizeof(used)); if(dfs(i)) { cnt++; } } return cnt; } int main() { int n,m,p; string a,b; while(cin>>n>>m>>p) { memset(map,0,sizeof(map)); memset(link,-1,sizeof(link)); cat_num = dog_num = 0; while(p--) { cin>>a>>b; //将喜欢猫的孩子划为A子集 if(a[0] == 'C') { cat[cat_num].like = a; cat[cat_num].dis = b; cat_num++; } //将喜欢狗的孩子划为B子集 if(a[0] == 'D') { dog[dog_num].like = a; dog[dog_num].dis = b; dog_num++; } } for(int i=0; i<cat_num; i++) { for(int j=0; j<dog_num; j++) { //若存在排斥关系 if(cat[i].like == dog[j].dis || cat[i].dis == dog[j].like) { map[i][j] = 1; } } } int cnt = maxMatch(); //最大独立集 = 顶点数 - 最大匹配数 cout<<cat_num+dog_num-cnt<<endl; } return 0; }
/* 题意:n个同学,一些男女同学会有缘分成为情侣,格式ni:(m) n1 n2 n3表示同学ni有缘与n1,n2,n3成为情侣,求集合中不存在有缘成为情侣的同学的最大同学数。 题解: 独立集:图的顶点集的子集,其中任意两点不相邻 最大独立集 = 顶点数 - 最大匹配数 但是这道题有一点不同,它没有告诉我们是男生还是女生,所以我们需要进行拆点,把一个学生拆成一个男生和一个女生,然后求出最大匹配后除以2即可。 */ #include <iostream> using namespace std; const int nMax = 500; int n; int map[nMax][nMax]; int link[nMax]; int useif[nMax]; bool can(int t) { for(int i = 0; i < n; ++ i) { if(!useif[i] && map[t][i]) { useif[i] = 1; if(link[i] == -1 || can(link[i])) { link[i] = t; return 1; } } } return 0; } int main() { //freopen("f://data.in", "r", stdin); while(scanf("%d", &n) != EOF) { memset(map, 0, sizeof(map)); memset(link, -1, sizeof(link)); int u, k, v; for(int i = 0; i < n; ++ i) { scanf("%d: (%d)", &u, &k); while(k --) { scanf("%d", &v); map[u][v] = 1; } } int num = 0; for(int i = 0; i < n; ++ i) { memset(useif, 0, sizeof(useif)); if(can(i)) ++ num; } printf("%d\n", n - num / 2); } return 0; }
最小路径覆盖
路径覆盖的定义是:在有向图中找一些路径,使之覆盖了图中的所有顶点,就是任意一个顶点都跟那些路径中的某一条相关联,且任何一个顶点有且只有一条路径与之关联,一个单独的顶点是一条路径.最小路径覆盖就是最少的路径覆盖
注意:每个点的入度出度都是1 也就是说 每个点决定1条边
通常一些可以建立类似 “从a转化到b的类型的题目 ” 可以用最小路径覆盖 即从a可以通过条件到达b 则可以用(个人理解而已)
因为 可以假设一开始 所有的点都是不连通的 如果需要联通 就需要n条边 之后用二分匹配每找到一条边(二分匹配是寻找包含边数最多 且每2条边没有交点) 就减少了一条边 假设找到了最多的边数为m 则还需要n-m条路径 所以最小路径覆盖=n-二分匹配。
以上为本人个人理解 不知道是否有误。
poj 1422
题意:一个地图上有n个小镇,以及连接着其中两个小镇的有向边,而且这些边无法形成回路。现在选择一些小镇空降士兵(1个小镇最多1个士兵),士兵能沿着边走到尽头,问最少空降几个士兵,能遍历完所有的小镇。
思路:匈牙利算法求最小路径覆盖:在一个有向图中,路径覆盖就是在图中找一些路径,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点,那么恰好可以经过图中的每个顶点一次且仅一次);解决此类问题可以建立一个二分图模型。把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',如果有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m
我们可以把问题转化为,在图上的边中选出一些边,使得每个点的如度与出度都不超过1。
我们开始在图上的每个点都放上伞兵,然后没选出一条边,就意味着有一个伞兵可以被取消掉了。也就是说需要的最少伞兵数=点总数-能选出的最大边数。
我们只要求最大边数即可。用二分图匹配,把每个点拆成两个点,分别加入二分图的两个点集,原图中一条由a到b的边在二分图中是一条由第一个点集中的第a个点到第二个点集中的第b个点。也就是第一个点集的点与出边有关,第二个与入边有关。匹配时也就保证了每个点的如度与出度都不超过1。求最大匹配即为能选出的最大边数。
/* */ #include<iostream> using namespace std; #define MAXV 130 int n,m; //交叉路口和街道的个数 int map[MAXV][MAXV]; int link[MAXV],use[MAXV]; bool dfs(int x){ int i,j; for(i=1;i<=n;i++){ if(map[x][i] && !use[i] ){ //拆点注意i不能等于x,否则就自己和自己匹配了 j=link[i]; use[i]=1; link[i]=x; if(j==-1 || dfs(j)) return true; link[i]=j; } } return false; } int hungary(){ int num=0; int i; memset(link,-1,sizeof(link)); for(i=1;i<=n;i++){ memset(use,0,sizeof(use)); if(dfs(i)) num++; } return n-num; } int main(){ int Case; int a,b,i; scanf("%d",&Case); while(Case--){ scanf("%d%d",&n,&m); memset(map,0,sizeof(map)); for(i=0;i<m;i++){ scanf("%d%d",&a,&b); map[a][b]=1; } printf("%d\n",hungary()); } return 0; }
/*题目大意:在一个坐标图中有一些点需要覆盖,每在一个点上覆盖就可在覆盖它上下左右4个点中任一个,问最小放几个。 分析:利用黑白染色法把每一个点都和与之相邻的4个点连边,就构成了一个二分图。要求的就是有最小的点数覆盖全部边,即求最小路径覆盖=最大独立集=所有点-最大匹配由此可以求出最优解。实现方法——匈牙利算法即可。注意的是,这里的点是所有可以放得点*2,而匹配数也是正常匹配数的二倍(A到B连了,B到A也连了),所以最后是n-最大匹配/2。 代码: */ #include<iostream> using namespace std; int h[41][11]; int g[405][405],visit[405],match[405]; char map[41][11]; int d[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; int n,m,ans,sum; int find(int a) { int i,j; for(i=1;i<=sum;i++) { if(!visit[i]&&g[a][i]==1) { visit[i]=1; if(match[i]==0||find(match[i])) { match[i]=a; return 1; } } } return 0; } int main() { int t,i,j,k,xx,yy; cin>>t; while(t--) { sum=ans=0; memset(g,0,sizeof(g)); memset(match,0,sizeof(match)); cin>>n>>m; for(i=0;i<n;i++) { cin>>map[i]; for(j=0;j<m;j++) { if(map[i][j]=='*') { sum++; h[i][j]=sum; } } } for(i=0;i<n;i++) for(j=0;j<m;j++) { if(map[i][j]=='*') { for(k=0;k<=3;k++) { xx=i+d[k][0]; yy=j+d[k][1]; if(xx>=0&&yy>=0&&xx<n&&yy<m&&map[xx][yy]=='*') g[h[i][j]][h[xx][yy]]=1; } } } for(i=1;i<=sum;i++) { memset(visit,0,sizeof(visit)); if(find(i)) ans++; } cout<<sum-ans/2<<endl; } return 0; }
题目大意:给出N(1000)个数字(<2^63 -1),从n中取出k个数,使得任意a,b属于k,a,b不形成整除关系。问k最大是多少
算法:有向图最小路径覆盖数
思路:因为要求取出一些点,使得他们之间没有整除关系,很容易想到利用整除关系建立一个图,然后看最多有多少个点能不相连,如果把图看作无向图,那么就很难想到做法,至少无向图最大点独立集是不能在1000的规模下运行的。如果a是b的约束,我们建立一条a向b的有向边,最后发现,要求的其实就是最小路径覆盖数。
最小路径覆盖数:在一个有向图中至少放几个人才能走到所有点(延边走,人可以放在任意位置,非官方解释)。
最小路径覆盖:在一个有向图中,最少用几条路径能把所有的点覆盖(路径不交叉,即每个点只属于一条路)。
当有向图无环时,可以用二分图最大匹配来求解。
最少路径覆盖=点数–二分图最大匹配
代码略
POJ2060
题目大意:有m个任务,每个任务有开始的时间(xx:xx),起点(a,b)与终点(c,d)
然后做完这个任务的时间是|a-c|+|b-d|分钟,如果一个车子在一天(00:00 to 23:59)之内做完i任务之后又能
做j任务,那么这个车子就做2个任务,依次类推下去
问最少需要多少辆车子把所有任务都做完
解题思路:最小路径覆盖
建图:i的开始时间+做完i任务的时间+做完i任务的终点到j任务的起点的时间<j开始的时间
那么做完i又能做完j,则i指向j
然后求最小路径覆盖即可
代码略
zoj 2521 LED Display
题意:七段数码显示器,一个数码上的7段灯可以选择灭掉一些后重复使用这个数码,
但灭掉的段不能再亮了。比如 6 可以 灭掉左小角的一段变成 5 来使用。
但自己不能重复使用,即最少灭一段才能再次使用。 现在按次序给出
一个要现实的数码序列,求需要的最少的灯数,使得能按所给的次序显示所有数码。
分析:
先确定所有数码可以通过灭掉一些段后形成的新数码:
原数码: 它可以形成的数码
0 : 1 7
1 :
2 :
3 : 1 7
4 : 1
5 :
6 : 5
7 : 1
8 : 0 1 2 3 4 5 6 7 9
9 : 1 3 4 5 7
对于输入的n长的序列,对第i个数,如果他后面的数j,可以由i形成,那么连一条有向边
i-j
那么问题转化会在一个有向无环图上找最少的路径数,有且仅有一次经过所有点。
而这个就是经典的最小路径覆盖问题了,可以用最大匹配来做,最小路径数 = n - 最大匹
POJ1548
【题意】
在一个矩形区域内,机器人从左上角出发,每次只能沿着现在位置的下方和右方移动,在移动过程中收拾垃圾,一直到区域的右下角,现在给出所有垃圾的位置,求解最少需要多少个机器人才能将这些垃圾清理成功.
【思路】
建图:以垃圾为点建立二分图,如果后一个垃圾的位置可以通过前一个垃圾的位置可达,则建立一条边,从而形成二分图,求二分图的最小路径覆盖问题。
最小顶点覆盖
poj3041
题目 棋盘上有N个点,每次可以拿走一行或者一列。 问最少多少次可以把棋盘上的所有点都拿走
思路 :
有向图最小路径覆盖数
将行作为左边的点,列作为右边的点,原图中的每个点形成一条边,将代表其行和列的点连接起来。
对已经建好的图求最大匹配
未完待续