好久之前就看了匈牙利,现在都快忘光了,于是写写\(\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
代码实现
匈牙利的代码超级简单,这也是它最大的优势,它运用了递归去不断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;
}