• 浅谈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
    ", km());
        return 0;
    }
    

    习题

    U53388

    运动员最佳匹配问题


    End

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

  • 相关阅读:
    谷歌浏览器解决跨域
    实现Linux共享Window文件
    linux安装显卡驱动
    jsduck 文档生成器
    linux 笔记
    Linux phpstorm 无法输入中文
    linux 安装composer
    Extjs动态生成表头(适用报表)
    关于git的配置与使用
    JSP解决中文乱码问题
  • 原文地址:https://www.cnblogs.com/enceladus-return0/p/10046380.html
Copyright © 2020-2023  润新知