几种概念:
二分图:是图论中的一种特殊模型。若能将无向图G=(V,E)的顶点V划分为两个交集为空的顶点集,并且任意边的两个端点都分属于两个集合,则称图G为一个为二分图。可以想象一下离散数学中的二部图。
匹配:一个匹配即一个包含若干条边的集合,且其中任意两条边没有公共端点。如下图,图3的红边即为图2的一个匹配。
1 最大匹配
在G的一个子图M中,M的边集中的任意两条边都不依附于同一个顶点,则称M是一个匹配。选择这样的边数最大的子集称为图的最大匹配问题,最大匹配的边数称为最大匹配数.如果一个匹配中,图中的每个顶点都和图中某条边相关联,则称此匹配为完全匹配,也称作完备匹配。如果在左右两边加上源汇点后,图G等价于一个网络流,最大匹配问题可以转为最大流的问题。解决此问的匈牙利算法的本质就是寻找最大流的增广路径。
2 最优匹配
最优匹配又称为带权最大匹配,是指在带有权值边的二分图中,求一个匹配使得匹配边上的权值和最大。一般X和Y集合顶点个数相同,最优匹配也是一个完备匹配,即每个顶点都被匹配。如果个数不相等,可以通过补点加0边实现转化。一般使用KM算法解决该问题。
3 最小覆盖
二分图的最小覆盖分为最小顶点覆盖和最小路径覆盖:
①最小顶点覆盖是指最少的顶点数使得二分图G中的每条边都至少与其中一个点相关联,二分图的最小顶点覆盖数=二分图的最大匹配数;
②最小路径覆盖也称为最小边覆盖,是指用尽量少的不相交简单路径覆盖二分图中的所有顶点。二分图的最小路径覆盖数=|V|-二分图的最大匹配数;
4 最大独立集
最大独立集是指寻找一个点集,使得其中任意两点在图中无对应边。对于一般图来说,最大独立集是一个NP完全问题,**对于二分图来说最大独立集=|V|-二分图的最大匹配数**。
基本概念—匈牙利算法
交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。*
增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)。
二、最大匹配与最小点覆盖
最小点覆盖:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边
最小割定理是一个二分图中很重要的定理:一个二分图中的最大匹配数等于这个图中的最小点覆盖数。
最小点集覆盖==最大匹配。在这里解释一下原因,首先,最小点集覆盖一定>=最大匹配,因为假设最大匹配为n,那么我们就得到了n条互不相邻的边,光覆盖这些边就要用到n个点。现在我们来思考为什么最小点击覆盖一定<=最大匹配。任何一种n个点的最小点击覆盖,一定可以转化成一个n的最大匹配。因为最小点集覆盖中的每个点都能找到至少一条只有一个端点在点集中的边(如果找不到则说明该点所有的边的另外一个端点都被覆盖,所以该点则没必要被覆盖,和它在最小点集覆盖中相矛盾),只要每个端点都选择一个这样的边,就必然能转化为一个匹配数与点集覆盖的点数相等的匹配方案。所以最大匹配至少为最小点集覆盖数,即最小点击覆盖一定<=最大匹配。综上,二者相等。
三、匈牙利算法
由增广路的性质,增广路中的匹配边总是比未匹配边多一条,所以如果我们放弃一条增广路中的匹配边,选取未匹配边作为匹配边,则匹配的数量就会增加。匈牙利算法就是在不断寻找增广路,如果找不到增广路,就说明达到了最大匹配。
(以上内容引自神犇大佬)
你可以理解为左边集合全是男生,右边集合全为女生,他们已经存在着某种联系,现在你要将其凑成情侣,问你最多能凑成多少对。
若初始化给你的关系是这样的:
(1)先给男生1号找妹子,发现她与女生1号有些联系,而女生一号也没有心仪对象,则他俩凑对
(2)然后在给男生2号找妹子,发现女生二号可以,凑对
(3)接下来是男生3号,发现男生3号与女生1号有一腿,则去看看可不可以给男生1号重新找一个妹子,让男生3号安稳的隔壁老王变男主人。
(黄色代表暂时拆掉这条线)
男生1号找到了女生2号,发现她也有中意的人,则他也要隔壁老王变男主人,给女生2号的原配找一个新的情人。(递归过程)
发现男生2号还可以找到女生3号这个小情人 ,则一切问题都迎刃而解。(回溯)
男2找女3
男1找女2
男3找女1
第三步的结果:
(4)给男4找妹子,但是按照第三步的步骤来发现他们实在没有办法在空出一个妹子来给男4,所以他只能孤独终老
这大概就是匈牙利算法的大体过程,主要的步骤还是在那个dfs身上会这一部分基本就掌握了这个算法。
代码模板:
//匈牙利算法
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
using namespace std;
const int N=1e6+10;
const int MAXN=800;
const double pai=4*atan(1);
int un,vn;//un 代表男生总数 vn 代表女生总数
int g[MAXN][MAXN];//邻接矩阵
int linker[MAXN];//女生中意的人
bool used[MAXN];//标记数组
int temp;
bool dfs(int u)//仅对男生使用dfs 这是该算法的关键点 精髓之处
{
for(int v=1;v<=vn;v++)
{
if(g[u][v]&&!used[v])
{
used[v]=true;
if(linker[v]==-1||dfs(linker[v]))
{
linker[v]=u;
return true;
}
}
}
return false;
}
int hungary()
{
int res=0;
memset(linker,-1,sizeof(linker));
for(int u=1;u<=un;u++)
{
memset(used,false,sizeof(used));
if(dfs(u))
res++;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>un>>vn)
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
int u,v;
cin>>u>>v;
g[u][v]=1;
}
cout<<hungary()<<endl;
}
return 0;
}