模板總結——二分圖最大權匹配
阿新 • • 發佈:2018-12-30
基本概念
完全二分圖:兩側頂點集大小相等的二分圖
完備匹配:所有頂點都是匹配點的匹配
最大權匹配:在帶權的完全二分圖中,匹配邊的權值和最大的完美匹配。
相等子圖:由原圖的點集和邊權等於頂點標號的和的邊集組成的圖。
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;
}