二分图概念
顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集。
无向图G为二分图的充分必要条件
G至少有两个顶点,且其所有回路的长度均为偶数。
最大匹配
给定一个二分图G,在G的一个子图M中,M的边集中的任意两条边都不依附于同一个顶点,则称M是一个匹配.
选择这样的边数最大的子集称为图的最大匹配问题.
最小覆盖
最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。最小覆盖=最大匹配。
简单路径:
如果一条路径上的顶点除了起点和终点可以相同外,其它顶点均不相同,则称此路径为一条简单路径;起点和终点相同的简单路径称为回路(或环)。
最小路径覆盖:
用尽量少的不相交简单路径覆盖有向无环图G的所有结点。
增广路(增广轨):
若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径(举例来说,有A、B集合,增广路由A中一个点通向B中一个点,再由B中这个点通向A中一个点……交替进行)。
增广路径的性质:
1 有奇数条边。
2 起点在二分图的左半边,终点在右半边。
3 路径上的点一定是一个在左半边,一个在右半边,交替出现。
4 整条路径上没有重复的点。
5 起点和终点都是目前还没有配对的点,而其它所有点都是已经配好对的。
6 路径上的所有第奇数条边都不在原匹配中,所有第偶数条边都出现在原匹配中。
7 最后,也是最重要的一条,把增广路径上的所有第奇数条边加入到原匹配中去,并把增广路径中的所有第偶数条边从原匹配中删除(这个操作称为增广路径的取反),则新的匹配数就比原匹配数增加了1个。
匈牙利算法的基本模式:
1、 初始时最大匹配为空
2、 while (找得到增广路径)
3、 do 把增广路径加入到最大匹配中。
二分图匹配中较为重要的三个公式:
二分图最小顶点覆盖 = 二分图最大匹配;
DAG图的最小路径覆盖 = 节点数(n)- 最大匹配数;
二分图最大独立集 = 节点数(n)- 最大匹配数;
样例:
/* 输入:第一行n,m,k,代表左子集有n个点,编号1~n,右子集有m个点,编号1~m,左右子集之间有k个关系。 然后有n行,每行有两个数a,b。表示a,b之间相连。 求二分图的最大匹配。 */ #include<iostream> #include<stdio.h> #include<string.h> using namespace std; int map[101][101]; int link[101]; //匹配结果中右子集的点的前驱,比如说link[5]=3, //代表右子集的5节点的前驱是左子集的3节点。 int vis[101];//标记右子集的节点在找当前增广链是是否找过 int n,m,k,s; int find(int x) { for(int i=1;i<=m;i++) { if(map[x][i]&&!vis[i])//当前节点在此次寻找增广链的过程中未被访问过。 { vis[i]=1; if(link[i]==0||find(link[i])) //当前右节点没有前驱,或者在往前的寻找过程中找到了增广链 { link[i]=x;//如果找到了增广链,那么if成立,这条增广链上的所有右节点才会被标记前驱。 return 1; } } } return 0; } int main() { int a,b; memset(link,0,sizeof(link)); memset(map,0,sizeof(map)); scanf("%d%d%d",&n,&m,&k); for(int i=0;i<k;i++) { scanf("%d%d",&a,&b); map[a][b]=1;//标记图中a,b的关系 } s=0; for(int i=1;i<=n;i++)//依次从左子集的点找增广链 { memset(vis,0,sizeof(vis)); if(find(i))s++;//存在增广链,匹配结果加1. } cout<<s<<endl; return 0; }