浅谈km算法解决二分图最大权匹配问题

浅谈km算法解决二分图最大权匹配问题


前置知识:

熟练的掌握二分图基本知识,会运用二分图染色和最大匹配。(好像并没什么用

二分图匹配大家都知道吧,二分图最大匹配大家也多知道吧,这里就不过多的进行讲述。其实是我懒得写。

可以看一下我这篇题解:超级英雄

对了推荐一个动态模拟二分图匹配的网址,超级好用:透彻


引子

下面引入这样一个问题:

GMP琛哥在放假回来后带了一堆零食,立刻被ghj1222,锤子和真硕看到了,他们说,琛哥,我看你这行吗?

琛哥没同意,然后他就把雷哥控制住了(QAQ好像哪里不对(大雾。

锤子,ghj,真硕就开始分琛哥的零食。每个人对不同的零食都有自己的偷税值。

怎么分零食,才能让所有人偷税值最大?

这就涉及到一个问题:二分图最大权匹配。

即:对于一个二分图,最大匹配的方式可能是不唯一的,如果给每一条边付上权值,会存在一个匹配使权值之和最大。


part\ one

引用某两个巨佬博客里的一句话:

二分图是特殊的网络流,二分图最大匹配可以用Dinic算法解决。二分图最大权匹配相当最小费用最大流可以用FF算法。

我那么菜,学不会网络流,还是学点简单的吧。

下面进入正题

km算法就可以来解决这个问题。不过有一个前提是这个匹配情况是要在完全匹配(就是各个点都能一一对应另一个点)。(如果只是想求最大权值匹配而不要求是完全匹配的话,请把各个不相连的边的权值设置为0。 )

二分图最大权匹配他也是二分图匹配啊,同时还是一种特殊的网络流,结合他们的思想,我们可以这样想:每次找最大边进行连边,那如果有一个点被比匹配过怎么办,用匈牙利算法来改变匹配。

所以km算法的思路就是:每次找最大的边连边,如果不能就换一条较大的。

下面要解决的一个问题就是:如何找该点对应的权值最大的边?


part\ two

继续上面的问题:如何找该点对应的权值最大的边?

km算法比较NB的地方来了:设立标杆

对于每个x_i,y_i,设立一个标杆C_x,C_y。

满足C_x+C_y>=w_{x,y}

  • 初始化

C_x=max(C_x,w_{x,y});

C_y=0;

  • 连边

用匈牙利算法,判断两点之间能否连线,再将最大边连线。这里把w_{x,y}0的条件换成了C_x+C_yw_{x,y}


part\ three

此时,有了一个新的名词——相等子图

相等子图:二分图中所有满足C_{x_i}+C_{y_i}=w[i][j]的边所构成的子图。

(弃坑口胡ing)


part\ four

if要求边权值最小的匹配呢???

我们可以把边权值取负值,得出结果后再取相反数就可以了。


Code

#include<cstdio>
#include<cstring>
#include<iostream>
#define N 520
#define inf 0x3f3f3f3f
using namespace std;
int n, nx, ny;
int link[N], lx[N], ly[N], slack[N];
int visx[N], visy[N], w[N][N];
bool dfs(int x) {
    visx[x] = 1;
    for(int y = 1; y <= n; ++ y) {
        if(visy[y]) continue;
        int tmp = lx[x] + ly[y] - w[x][y];
        if(tmp == 0) {
            visy[y] = 1;
            if(link[y] == -1 || dfs(link[y])) {
                link[y] = x; return 1;
            }
        }else if(slack[y] > tmp){
            slack[y] = tmp;
        }
    }
    return 0;
}
int km() {
    int i, j;
    memset(link, -1, sizeof link);
    memset(ly, 0, sizeof ly);
    for(i = 1; i <= nx; ++ i)
        for(j = 1, lx[i] = -inf; j <= ny; ++ j)
        if(w[i][j] > lx[i]) lx[i] = w[i][j];
    for(int x = 1; x <= nx; ++ x) {
        for(i = 1; i <= ny; ++ i) slack[i] = inf;
        while(1) {
            memset(visx, 0, sizeof visx);
            memset(visy, 0, sizeof visy);
            if(dfs(x)) break;
            int d = inf;
            for(i = 1; i <= ny; ++ i)
                if(!visy[i] && d > slack[i]) d = slack[i];
            for(i = 1; i <= nx; ++ i) 
                if(visx[i]) lx[i] -= d;
            for(i = 1; i <= ny; ++ i)
                if(visy[i]) ly[i] += d;
                else slack[i] -= d;
        }
    }
    int res = 0;
    for(i = 1; i <= ny; ++ i)	
        if(link[i] != -1) res += w[link[i]][i];
    return res;
}
int main()
{
    cin >> n;
    nx = ny = n;
    for(int i = 1; i <= n; ++ i)
        for(int j = 1; j <= n; ++ j)
            scanf("%d", &w[i][j]);
    printf("%d\n", km());
    return 0;
}

习题

U53388

运动员最佳匹配问题


End

大概是写的最后一篇博客了吧QAQ。

posted @ 2018-11-30 20:45  enceladus  阅读(147)  评论(0编辑  收藏  举报

Contact with me