匈牙利演算法,能在 \(O(Lm+R)\) 的複雜度處理二分圖最大匹配的問題,其中 \(L\) 是左部點個數,\(R\) 是右部點個數,\(m\) 是邊數,馬良極短,吊打網路流。
首先我們有很多關鍵點能想到這個,要麼是涉及到匹配,或者是涉及到需要取出一些環的問題,這個是一個極好的處理方式。
演算法流程就是一種調整法,考慮就是對於一個點,暴力去嘗試匹配,如果不能匹配上它就失配了。暴力的過程則分為兩種:
-
若該右部點沒有匹配,直接匹配該右部點。
-
若該右部點有匹配,嘗試讓原來匹配的那個點找到新的匹配。
以下是簡短的程式碼:
#include<bits/stdc++.h>
using namespace std;
const int M=1e3+5;
int mtch[M];
int visT[M];
int n,m,e,x,y;
vector<int> E[M];
int ans;
bool dfs(int now,int tg){
if(visT[now]==tg)return 0;
visT[now]=tg;
for(auto p:E[now])if(!mtch[p]||dfs(mtch[p],tg)){
mtch[p]=now;
return 1;
}
return 0;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>e;
for(int i=1;i<=e;i++)cin>>x>>y,E[x].push_back(y);
for(int i=1;i<=n;i++)ans+=dfs(i,i);
cout<<ans;
return 0;
}
其中 dfs
函式就是嘗試匹配的最主要過程。遞迴的過程就是找新匹配的過程。為什麼可以用 vis
陣列?因為再次訪問到只有兩種情況,要麼是再次嘗試替換這個點,要麼是替換成環,如果我們找到匹配就退出,那麼這兩種情況一定無解。
例題:CF387D George and Interesting Graph
George 喜歡圖,因此它想讓圖更有趣。他認為有趣的圖滿足以下條件:
- 沒有重邊;
- 存在結點 \(v\)(稱為中心),使得對於圖中的任意結點 \(u\),都有邊 \((u,v)\) 和 \((v,u)\),注意自環 \((v,v)\) 也應該存在;
- 除去中心外,每個點的入度和出度都恰好為 \(2\);
顯然很少有圖有趣,但 George 可以把圖變得有趣:每次他可以增加一條邊或者刪除一條已經存在的邊。
現在給出圖 \(G\),George 想知道他最少做多少次操作可以使它變得有趣。
\(n\leq 500,m\leq 1000\)。
列舉中心,剩下就變成嘗試連出一些簡單環使得一個點在且僅在一個簡單環裡,這個直接上二分圖匹配就能求出最多有多少條已有邊在這個系統裡。