KM算法(二分图完备匹配下的最大权匹配)

阅读本文之前,首先假设您已经理解匈牙利算法,如果不理解友链在这

=>我是匈牙利算法<=

有时间会将匈牙利算法的博客补上的。

KM算法

KM算法求的是二分图完备匹配下的最大权匹配。

在二分图中,x点集中的所有点都有对应的匹配 且 y 点集中所有的点都有对应的匹配,则称该匹配为完备匹配。但是完备匹配可能不止一种,KM算法就是为了求出,其中边权和最大的一组。

KM算法的思路非常简单,核心可以看做这一句:

从最大权的匹配(不一定是完备匹配)开始,判断是否可以构成完备匹配。
如果可以则为答案,如果不可以则判断第二大、第三大、第四大......
直到找到答案或者没有更多匹配方案为止。

或许以上的说法还是有些迷糊的,那我们就看下面这一种更为清晰的说法。

首先找到从x中各个点出发的,各自的最大的一条边,构成匹配。
如果该匹配是完备匹配,则找到答案。
否则,加入所有未在该匹配中,最大的一条边,重新判断。

通过这样,我们可以保证,对于任意一个匹配,没有比当前匹配权值和更大的完备匹配。

算法的大致过程如下:

1.在原图中判断是否可以构成完备匹配,如果可以,步骤2。

2.向集合中加入从x中各个点出发的,各自的最大的一条边,构成匹配。

3.判断当且匹配是否为完备匹配,如果是,输出答案,否则,接步骤4

4.加入未在匹配中,且权值最大的一条边,重复3。

实现

显然,对于每条边进行标记是否在当且匹配中,是一个效率低下的方法。那么我们应该用什么样的方法来完成这一操作呢?

我们可以设置tagx[] tagy[] 这两个数组,对于x,y之间的边,一定满足

w[x][y]==tagx[x]+tagy[x];

也就是说,当且仅当以上条件成立时,我们才认为这是一条在匹配中的边。

初值:
假设从xi出发,距离最远的点为yi,那么tagx[xi]=w[xi][yi],tagy[yi]=0;

修改:
我们找到最小的 d = tagx[ i ]+tagy[ j ]-w[ i ][ j ] , 其中w[ i ][ j ]没有这条边没有被加入过,我们可以用两个marky[],markx[]数组判断这一点。

对于所有已经访问过的xi,yi,我们进行如下操作。

tagx[xi]-=d;
tagy[yi]+=d;

显然,对于已经在匹配中的一条边w[ xj ] [ yj ] 而言,由于d的增减可以通过tagx [ xj ] + tag[ yj ] 来抵消,所以并不影响。而对于新加入的这条边 w [ xj ][ yj ]来说, xj 被访问过,而yj 没有,所以tagx [ xj ] + tagy[ yj ] == w[ xj ][ yj ]

最后附上代码:

//KM.cpp
//
//Glasses
#include <map>
#include <queue>
#include <cmath>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL int
#define MAXN 300 +10
#define MOD 1000000007
#define INF 0x7f7f7f7f
#define inf 0x7f7f7f7f
#define FOR(i,a,b) for(LL i = (a), i##end = (b); i <= i##end; ++i)
#define ROF(i,a,b) for(LL i = (a), i##begin = (b); i >= i##begin; --i)
#define output(a) printf("%d\n",(a));
#define input(a) scanf("%d",&(a));
LL read()
{  
    LL x=0;char ch=getchar();  
    while(ch<'0'||ch>'9')ch=getchar();  
    while(ch>='0' && ch<='9')x=x*10+ch-'0',ch=getchar();  
    return x;
}

struct KM
{
    bool markx[MAXN],marky[MAXN];
    LL tagx[MAXN],tagy[MAXN],match[MAXN],w[MAXN][MAXN];
    LL n,m;

    void init(LL inn,LL inm)
    {
        this->n=inn;this->m=inm;
        FOR(i,0,MAXN-1)FOR(j,0,MAXN-1)w[i][j]=0;
    }

    void add_edge(LL x,LL y,LL z)
    {
        w[x][y]=max(w[x][y],z);
    }

    bool dfs(LL x)
    {
        markx[x]=1;
        FOR(y,1,m)
            if(!marky[y]&&tagx[x]+tagy[y]==w[x][y])
            {
                marky[y]=1;
                if(!match[y]||dfs(match[y]))
                {
                    match[y]=x;
                    return 1;
                }
            }
        return 0;
    }

    LL solve()
    {
        memset(tagy,0,sizeof(tagy));
        FOR(i,1,n)
        {
            tagx[i]=-INF;
            FOR(j,1,m)tagx[i]=max(tagx[i],w[i][j]);
        }

        memset(match,0,sizeof(match));
        FOR(k,1,n)
        {
            while(1)
            {
                memset(markx,0,sizeof(markx));
                memset(marky,0,sizeof(marky));
                if(dfs(k))break;
                LL d=INF;
                FOR(i,1,n)if(markx[i])
                    FOR(j,1,m)if(!marky[j])
                    d=min(d,tagx[i]+tagy[j]-w[i][j]);
                if(d==INF)return -1;
                FOR(i,1,n)if(markx[i])tagx[i]-=d;
                FOR(i,1,m)if(marky[i])tagy[i]+=d;
            }
        }
        LL sum=0;
        FOR(i,1,m)if(match[i])sum+=w[match[i]][i];
        return sum;
    }
}e;

错误是我的,请让我吃掉:(

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值