Kuhn-Munkras 算法,掌握这个算法之前你需要掌握匈牙利算法;
这个算法挺难懂得,我自己也是云里雾里,建议参考一些大牛的博客;
这位博主也是转载的,内容是值得肯定的:点击打开链接(尤其是相等子图的完备匹配是原图最佳匹配的证明,这个是算法的基础);
KM算法流程如下:
1)可行点标:每个点有一个标号,记lx[i]为X方点i的标号,ly[j]为Y方点j的标号。如果对于图中的任意边edge(i, j)都有lx[i]+ly[j]>=weight(i,j),则这一组点标是可行的。特别地,对于lx[i]+ly[j]=weight(i,j),称为可行边(也就是相等子图里的边)
2)KM 算法的核心思想就是通过修改某些点的标号(但要满足点标始终是可行的),不断增加图中的可行边总数,直到图中存在仅由可行边组成的完全匹配为止,此时这个 匹配一定是最佳的;
3)一开始,求出每个点的初始标号:lx[i]=max{e.W|e.x=i}(即每个X方点的初始标号为与这个X方点相关联的权值最大的边的权值),ly[j]=0(即每个Y方点的初始标号为0)。这个初始点标显然是可行的,并且,与任意一个X方点关联的边中至少有一条可行边
4)然后,从每个X方点开始DFS增广。DFS增广的过程与最大匹配的Hungary算法基本相同,只是要注意两点:一是只找可行边,二是要把搜索过程中遍历到的X方点全部记下来(可以用visx),以进行后面的修改
5)增广的结果有两种:若成功(找到了增广路),则该点增广完成,进入下一个点的增广。若失败(没有找到增广路),则需要改变一些点的标号,使得图中可行边的 数量增加(见注释一,代码下面)
6)修改后,继续对这个X方点DFS增广,若还失败则继续修改,直到成功为止;
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 305;
int n, nx, ny;
int lx[maxn], ly[maxn], visx[maxn], visy[maxn], G[maxn][maxn], match[maxn];
int findpath(int x){
visx[x] = 1;
for(int y = 0; y < ny; ++y){
if(!visy[y] && (lx[x] + ly[y] == G[x][y])){ //(x, y)在相等子图中;
visy[y] = 1;
if(match[y] == -1 || findpath(match[y])){
match[y] = x;
return 1;
}
}
}
return 0;
}
void KM(){ //
for(int x = 0; x < nx; ++x){
while(true){
memset(visx, 0, sizeof(visx));
memset(visy, 0, sizeof(visy));
if(findpath(x)) //如果找到增广路;
break;
else{ //匹配失败, lx[]-delta, ly+delta;(x, y在增广路中); (这里补充证明,见注释1)
int delta = INF;
for(int i = 0; i < nx; ++i){
if(visx[i]) //x在增广路中;
for(int j = 0; j < ny; ++j){
if(!visy[j]){ //y不在增广路中;
delta = min(delta, lx[i]+ly[j]-G[i][j]);
}
}
}
for(int i = 0; i < nx; ++i) if(visx[i]) lx[i] -= delta;
for(int i = 0; i < ny; ++i) if(visy[i]) ly[i] += delta;
}
}
}
}
int main()
{
while(scanf("%d", &n) != EOF){
nx = ny = n;
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j){
scanf("%d", &G[i][j]);
}
}
memset(lx, 0, sizeof(lx));
for(int i = 0; i < nx; ++i)
for(int j = 0; j < ny; ++j)
lx[i] = max(lx[i], G[i][j]);
memset(ly, 0, sizeof(ly));
memset(match, -1, sizeof(match));
KM();
int ans = 0;
for(int i = 0; i < n; ++i){
if(match[i] != -1)
ans += G[match[i]][i];
}
printf("%d\n", ans);
}
return 0;
}
注释一:
对于正在增广的增广路径上属于集合X的所有点减去一个常数delta,属于集合Y的所有点加上一个常数delta。
对于图中任意一条边edge(i,j) (其中i在集合X中,j在集合Y中)权值为weight(i,j)
(1)如果i和j都属于增广路,那么lx[i] - delta + ly[j] + delta = lx[i] + ly[j]值不变,也就说edge(i,j)可行性不变,原来是相等子图的边就还是,原来不是仍然不是。
(2)如果i属于增广路,j不属于增广路,那么lx[i] - delta + ly[j]的值减小,也就是原来这条边不在相等子图中(否则j就会被遍历到了),现在可能就会加入到相等子图。(因为之前lx[i] + ly[j] >= weight(x,y),现在lx[i] + ly[j]的值减小了,有可能会满足lx[i] + ly[j] == weight(i,j),这是相等子图中的边满足的条件)
(3)如果i不属于增广路,j属于增广路,那么lx[i] + ly[j] + delta的值增大,也就是说原来这条边不在相等子图中(否则j就会被遍历到了),现在还不可能加入到相等子图。
(4)如果i,j都不属于增广路,那么lx[i] + ly[j] = lx[i] + ly[j]值不变,可行性不变
所以,经过处理,lx[], ly[] 的可行性不会发生改变,增加了加入到相等子图的记录;