二分图匹配的匈牙利算法

好久之前就看了匈牙利,现在都快忘光了,于是写写\(\tt{Blog}\)加深一下印象


匈牙利算法简介

按照我的惯例,引用一下某度百科

匈牙利算法(Hungarian method)是由匈牙利数学家Edmonds于1965年提出,因而得名。匈牙利算法是基于Hall定理中充分性证明的思想,它是二分图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径二分图最大匹配的算法

求二分图最大匹配常用的有两种算法,一种是网络流,一种是匈牙利。时间复杂度上来讲,网络流要比匈牙利快的多。但是从代码复杂度上来讲,匈牙利要比网络流好写的多。

匈牙利时间复杂度:邻接矩阵为\(O(n^3)\),邻接表为\(O(nm)\)

算法思想

上面已经说过了,匈牙利是用增广路径来求二分图最大匹配。看着很高大上,但是当你理解后,你会发现匈牙利其实十分暴力

让我们模拟一下

对于上面这个图,我们先让\(0->6\),没有问题


接下来让\(1->7\),并没有发生冲突,也没有问题


接下来让\(2->6\),但是6已经和1匹配了,但2并不管这些,后来者居上,于是1就被NTR了 2和1匹配,而0就只能去找它的二号对象\(->7\)


接下来3、4都比较平和,没有强制让位的情况发生。

再接下来的情况,我就懒得模拟了,诸位客官见谅。

其实匈牙利的算法核心就是NTR别人和不断被NTR

代码实现

Luogu P3386【模板】二分图匹配

匈牙利的代码超级简单,这也是它最大的优势,它运用了递归去不断NTR别人寻找增广路。我是用邻接表实现的

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,k;
struct zzz{
    int t,
        nex;
}e[1001*1001*4];
int head[4001],tot;
void add(int x,int y)
{
    e[++tot].t=y;
    e[tot].nex=head[x];
    head[x]=tot;
}
int ans;
bool vis[4001];
int pp[4001]; //pp[i] 用来存i的当前匹配的对象是谁
bool find(int x) //寻找增广路
{
    for(int i=head[x];i;i=e[i].nex)
    {
        int to=e[i].t;
        if(!vis[to])
        {
            vis[to]=1;
            if(!pp[to]||find(pp[to])) //抢占他人的匹配对象,然后再为被抢的人找一个新的对象
            {
                pp[to]=x;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=k;i++)
    {
        int x,y; scanf("%d%d",&x,&y);
        if(y>m||x>n) continue;
        add(x,y);
    }
    for(int i=1;i<=n;i++) //为每个节点寻找匹配对象
    {
        memset(vis,0,sizeof(vis));
        if(find(i))
          ans++;
    }
    
    printf("%d",ans);
    
    return 0;
}

Luogu P2756 飞行员配对方案问题
匈牙利+输出方案

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
inline int read()
{
    int k=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar())
      if(c=='-')
        f=-1;
    for(;isdigit(c);c=getchar())
      k=(k<<3)+(k<<1)+c-48;
    return k*f;
}
struct zzz{
    int t,
        nex;
}e[10001*2]; int head[101],tot;
inline void add(int x,int y)
{
    e[++tot].t=y;
    e[tot].nex=head[x];
    head[x]=tot;
}
bool vis[101];int lin[101];
bool find(int x)
{
    for(int i=head[x];i;i=e[i].nex)
    {
        if(!vis[e[i].t])
        {
            vis[e[i].t]=1;
            if(!lin[e[i].t]||find(lin[e[i].t]))
            {
                lin[e[i].t]=x;
                return 1;
            }
        }
    }
    return 0;
}
int ans;
int main()
{
    int n,m; m=read(),n=read();
    while(1)
    {
        int x=read(),y=read();
        if(x==-1&&y==-1)
          break;
        add(x,y); add(y,x);
    }
    for(int i=1;i<=n;i++)
    {
        memset(vis,0,sizeof(vis));
        if(find(i)) ans++;
    }
    if(ans==0)
      cout<<"No Solution!";
    cout<<ans/2<<endl;
    
    //输出方案
    for(int i=m;i<=n;i++)
    {
        if(lin[i]>0&&lin[i]<=m&&ans)
        {
            ans--;
            printf("%d %d\n",lin[i],i);
        }  
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/wxl-Ezio/p/9102391.html