1. 程式人生 > >模板總結——二分圖最大權匹配

模板總結——二分圖最大權匹配

基本概念

完全二分圖:兩側頂點集大小相等的二分圖

完備匹配:所有頂點都是匹配點的匹配

最大權匹配:在帶權的完全二分圖中,匹配邊的權值和最大的完美匹配。

相等子圖:由原圖的點集和邊權等於頂點標號的和的邊集組成的圖。

KM演算法原理

設左側頂點的頂標集為{Ui},右側頂點的頂標集為{Vj}。

定理:如果含有頂標的二分圖的相等子圖存在完備匹配,則該匹配就是原圖的最大權匹配。

KM演算法通過不斷地調整{Ui}和{Vj},直到其相等子圖存在一個完備匹配,然後用匈牙利演算法求出這個完備匹配。

KM演算法流程

流程:
(1)初始化可行頂標的值
(2)用匈牙利演算法尋找完備匹配
(3)若未找到完備匹配,則修改可行頂標的值
(4)重複(2)(3)直到找到相等子圖的完備匹配為止

虛擬碼:
(1)左側頂標設為所有相連邊的最大邊權,右側頂標設為0
(2)對應圖左側的每一頂點u
(2.1)清空所有標記
(2.2)如果Find(u),繼續
(2.3)否則,修改頂標集
(2.3.1)d=min{Ui+Vj-Wij},對於所有左側被標記右側未標記的邊
(2.3.2)對於所有被標記的點,左側的頂標-=d,右側的頂標+=d
(2.3.3)回到(2.1)
(3)根據輸出的匹配算出最大權

模板

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define INF 0x3f3f3f3f using namespace std; const int maxn=330; int from[maxn],w[maxn][maxn]; int lx[maxn],ly[maxn],visx[maxn],visy[maxn],slack[maxn]; int nx,ny; ///定義slack[y]=min{lx[x]+ly[y]-w[x][y]},x已訪問,y未訪問 bool Find(int u) { visx[u]=1; for(int v=0;v<ny;v++)if(!visy[v]) { int
tmp=lx[u]+ly[v]-w[u][v]; if(tmp==0) { visy[v]=1; if(from[v]==-1 || Find(from[v])) { from[v]=u; return true; } } else slack[v]=min(slack[v],tmp); } return false; } int KM() { for(int i=0;i<nx;i++) { ly[i]=0; lx[i]=-INF; for(int j=0;j<ny;j++) lx[i]=max(lx[i],w[i][j]); } memset(from,-1,sizeof(from)); for(int u=0;u<nx;u++) { for(int i=0;i<ny;i++) slack[i]=INF; while(true) { memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(Find(u)) break; int d=INF; for(int i=0;i<ny;i++)if(!visy[i]) d=min(d,slack[i]); for(int i=0;i<nx;i++)if(visx[i]) lx[i]-=d; for(int i=0;i<ny;i++) { if(visy[i]) ly[i]+=d; else slack[i]-=d; } } } int ans=0; for(int i=0;i<ny;i++) if(from[i]!=-1) ans+=w[from[i]][i]; return ans; } int main() { int n; while(~scanf("%d",&n)) { nx=ny=n; for(int i=0;i<n;i++) for(int j=0;j<n;j++) scanf("%d",&w[i][j]); printf("%d\n",KM()); } return 0; }

模板題