关于二分图匹配的算法
关于二分图匹配,我目前学习了两种算法,记录下来以备复习。
- 匈牙利算法
- 网络流跑二分图
首先介绍一下什么是二分图,二分图是两组点,一组内点不连边的一张图。形象的理解,可以理解为两组人,一组是男生,一组是女生,男生和女生之间连边,表示互有好感,而男男和女女之间是不会连边的默认性取向正常。就像这张图
那么匹配就是指我把连边的两个人匹配起来,但是匹配是要求不能存在渣男和渣女的,也就是一个人只能对应一个,即使你和多个人连边,你也只能选择一个。
所以二分图匹配就是从给出的边中选取一些边,没有一个人和两个人连边,都是一一连边,这样的最大边数。
接下来说一下匈牙利算法,匈牙利算法是一个协商和妥协的过程,假设我们已经得到一张连好边的图[推荐用前向星 h数组+edge结构体 存边
存单向边其实就可以了 那么我们假设从左边的点往右边的点一个一个连边
- Dfs每一个左部点,看它能走到哪个点
- 如果这个点已经有匹配了,就Dfs它的匹配点,如果能退让,就优先退让
- 如果这个点的匹配点不能找到新匹配点,那就不会退让,因为不会变的更优
- 如果你找不到匹配点,就单着吧
匈牙利是一个找增广路的过程,增广路就可以认为是能使答案增加的路,你发现如果能退让,那么原先的匹配不减少,还多了一个,所以优先退让一定更优。
当没有增广路时,也就是无法退让时,答案达到最大。
贴一份luogu P3386模板代码
#include<bits/stdc++.h>
using namespace std;
const int E=5E4+8,N=508;
int n,m,e,ans=0;
int mch[N],vis[N];
//存它的匹配点,因为我们上文说了,如果这个点匹配了,就Dfs它的匹配点,所以我们需要记录匹配点才能dfs[见第二步
//vis数组就是记录时间戳,记录这一次dfs点的编号,也就是这一次dfs访问过,这是为了防止假设点A找到点B,看一眼点B的匹配点C,然后Dfs C找到点B,看一眼点B的匹配点C...
//所以找过的点就不用找了,要么正在查找,要么就是不能改变,防止反复横跳
int h[N],cnt=0;
struct edg{
int nxt,to;
}ed[E];
//存边
int flg=0;
void add(int u,int v){
ed[++cnt]=(edg){h[u],v};
h[u]=cnt;
}
bool Dfs(int x,int tag){//bool是为了判断是不是能找到
if(vis[x]==tag)return 0;
//如果搜到过,就跳掉,说明不可能,你别想从我这里得到答案[就如同上文,B找到C找到B找到C... 这种其实是没有找到,所以return 0
vis[x]=tag;//打标记
for(int p=h[x];p;p=ed[p].nxt){
int v=ed[p].to;//遍历所有点
if(mch[v]==0 || Dfs(mch[v],tag)){//如果未匹配,或者其匹配点可以退让,则我匹配之
mch[v]=x;
return 1;//可以找到
}
}
return 0;//实在找不到,单着吧
}
int main(){
scanf("%d%d%d",&n,&m,&e);
if(n>m){swap(n,m);flg=1;}
//一个优化,从多的点Dfs 因为复杂度是O(ne+m) 左部点*边+右部点 所以让左部点少一点更优
//我一开始写成n<m 跑了9ms 后来改了一下 变成2ms了,还是有用的
//其实网络流也是2ms[网络流天下第一]直接跑dinic多好
for(int u,v,i=1;i<=e;i++){
scanf("%d%d",&u,&v);
if(flg)add(v,u);//如果左右交换 反着加边
else add(u,v);
}
for(int i=1;i<=n;i++){
if(Dfs(i,i)){//Dfs所有左点,如果可以就匹配+1
ans++;
}
}
printf("%d\n",ans);
return 0;
}
好了,先到这,网络流回头写
我不想就这样沦陷,迷失在黑夜,我将燃烧这生命,就算再壮烈。