hdu 2255

本文详细介绍了Kuhn-Munkras算法的工作原理及其在寻找最佳匹配问题中的应用。首先阐述了匈牙利算法作为基础,接着深入解析了KM算法的具体步骤,包括可行点标的定义、初始点标设置、DFS增广过程以及关键的标号调整策略。

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[] 的可行性不会发生改变,增加了加入到相等子图的记录;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值