最近研究了下KM算法求二分图的最大权匹配,网上一堆人讲slack数组可以优化KM算法到O(n3),仔细研究了下发现他们的写法和直接找min值,复杂度本质上是一样的,感觉有点画蛇添足。
看了一篇大佬的文章,找到了这个真O(n3)的写法,它通过不重复构造新的增广路径将KM算法优化到了O(n3),这里我自己实现了一遍。以下是实现代码。
大佬的链接
#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define maxn 10000
int nx,ny;
bool visx[maxn],visy[maxn];
int fa[2*maxn];
int slack[maxn];
int match[maxn];
int cx[maxn],cy[maxn];
int G[maxn][maxn];
int dfs(int x)
{
visx[x]=1;
for(int y=0;y<=ny-1;y++)
{
if(!visy[y])
{
int tmp=cx[x]+cy[y]-G[x][y];
if(tmp==0)
{
visy[y]=1;
fa[y+nx]=x;
if(match[y]==-1)
{
return y+nx;
}
fa[match[y]]=y+nx;
int res=dfs(match[y]);
if(res>=0)
{
return res;
}
}
else
{
slack[x]=min(slack[x],tmp);
}
}
}
return -1;
}
void km()
{
memset(match,-1,sizeof(match));
for(int i=0;i<=nx-1;i++)
{
for(int j=0;j<=ny-1;j++)
{
cx[i]=max(cx[i],G[i][j]);
}
}
for(int x=0;x<=nx-1;x++)
{
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
memset(slack,INF,sizeof(slack));
memset(fa,-1,sizeof(fa));
bool first=true;
int end=-1;
while(1)
{
if(first)
{
first=false;
end=dfs(x);
}
else
{
for(int i=0;i<=nx-1;i++)
{
if(slack[i]==0)
{
slack[i]=INF;
end=dfs(i);
if(end>=0)
break;
}
}
}
if(end>=0)
{
int p=end;
while(p!=-1)
{
match[p-nx]=fa[p];
p=fa[fa[p]];
}
break;
}
else
{
int d=INF;
for(int i=0;i<=nx-1;i++)
{
d=min(d,slack[i]);
}
for(int i=0;i<=nx-1;i++)
{
if(visx[i])
{
cx[i]-=d;
slack[i]-=d;
}
}
for(int i=0;i<=ny-1;i++)
{
if(visy[i])
{
cy[i]+=d;
}
}
}
}
}
}
int main()
{
int n;
scanf("%d",&n);
nx=ny=n;
for(int i=0;i<=nx-1;i++)
{
for(int j=0;j<=ny-1;j++)
{
int tmp;
scanf("%d",&tmp);
G[i][j]=tmp;
}
}
km();
for(int i=0;i<=ny-1;i++)
{
printf("%d ",match[i]);
}
printf("\n");
int sum=0;
for(int i=0;i<=ny-1;i++)
{
sum+=G[match[i]][i];
}
printf("%d",sum);
return 0;
}