题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2255
分析
这是一道KM算法的裸题。
算法讲解链接:https://blog.csdn.net/chenshibo17/article/details/79933191
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define INF 0x3f3f3f3f
#define N 300 + 10
int w[N][N],n;
int lx[N],ly[N];//顶标
int match[N];
int s[N],t[N],slack[N];//s[i]和t[i]为左右第i个点是否匹配(一趟匹配中)
bool find(int i)
{
s[i] = 1;
for(int j = 1; j <= n; j++) {
if(t[j]) continue;//这个点被访问过了
int tmp = lx[i] + ly[j] - w[i][j];
if(!tmp) {//如果顶标之和等于权值
t[j] = 1;
if(!match[j] || find(match[j])) {
match[j] = i;
return true;
}
} else slack[j] = min(slack[j],tmp);//存储与左边的点匹配成功的最小距离
//注意这里是根据已经访问过的左边的点得出的值,所以下面slack要同样减少a
}
return false;
}
void update()
{
int a = INF;
for(int i = 1; i <= n; i++)
//右边的点不在这趟匹配中
if(!t[i]) a = min(a,slack[i]);//顶标需要降低的最小值
for(int i = 1; i <= n; i++) {
if(s[i]) lx[i] -= a;
if(t[i]) ly[i] += a;
else slack[i] -= a;//右边没有访问过的点距离与左边的点匹配成功少了a
}
}
void KM()
{
for(int i = 1; i <= n; i++) {
match[i] = lx[i] = ly[i] = 0;
for(int j = 1; j <= n; j++)
lx[i] = max(lx[i],w[i][j]);//左边顶标赋值为邻边权重的最大值,右边的顶标赋值为0
}
for(int i = 1; i <= n; i++) {
memset(slack,INF,sizeof(slack));
while(1) {
//s,t清空,进行下一趟匹配的尝试
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
if(find(i)) break;//如果匹配成功
else update();//更新顶标
}
}
}
int main()
{
while(~scanf("%d",&n)) {
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
scanf("%d",&w[i][j]);
KM();
int ans = 0;
for(int i = 1; i <= n; i++)
ans += w[match[i]][i];
printf("%d\n",ans);
}
return 0;
}