二分圖匹配的匈牙利算法
阿新 • • 發佈:2018-05-28
就是 ref inline 算法 return sca log 數學家 method
好久之前就看了匈牙利,現在都快忘光了,於是寫寫\(\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;
}
二分圖匹配的匈牙利算法