基本概念
完全二分图:两侧顶点集大小相等的二分图
完备匹配:所有顶点都是匹配点的匹配
最大权匹配:在带权的完全二分图中,匹配边的权值和最大的完美匹配。
相等子图:由原图的点集和边权等于顶点标号的和的边集组成的图。
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;
}