关于二分图匹配的算法

关于二分图匹配,我目前学习了两种算法,记录下来以备复习。
  • 匈牙利算法
  • 网络流跑二分图
    首先介绍一下什么是二分图,二分图是两组点,一组内点不连边的一张图。形象的理解,可以理解为两组人,一组是男生,一组是女生,男生和女生之间连边,表示互有好感,而男男和女女之间是不会连边的默认性取向正常。就像这张图

    那么匹配就是指我把连边的两个人匹配起来,但是匹配是要求不能存在渣男和渣女的,也就是一个人只能对应一个,即使你和多个人连边,你也只能选择一个。
    所以二分图匹配就是从给出的边中选取一些边,没有一个人和两个人连边,都是一一连边,这样的最大边数。
    接下来说一下匈牙利算法,匈牙利算法是一个协商和妥协的过程,假设我们已经得到一张连好边的图[推荐用前向星 h数组+edge结构体 存边
    存单向边其实就可以了 那么我们假设从左边的点往右边的点一个一个连边
  1. Dfs每一个左部点,看它能走到哪个点
  2. 如果这个点已经有匹配了,就Dfs它的匹配点,如果能退让,就优先退让
  3. 如果这个点的匹配点不能找到新匹配点,那就不会退让,因为不会变的更优
  4. 如果你找不到匹配点,就单着吧
    匈牙利是一个找增广路的过程,增广路就可以认为是能使答案增加的路,你发现如果能退让,那么原先的匹配不减少,还多了一个,所以优先退让一定更优。
    当没有增广路时,也就是无法退让时,答案达到最大。
    贴一份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;
}

好了,先到这,网络流回头写

posted @ 2020-08-22 08:21  Z_char  阅读(220)  评论(0编辑  收藏  举报