匈牙利。

xingyu_xuan發表於2024-10-11

匈牙利演算法,能在 \(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\)

列舉中心,剩下就變成嘗試連出一些簡單環使得一個點在且僅在一個簡單環裡,這個直接上二分圖匹配就能求出最多有多少條已有邊在這個系統裡。