Kuhn-Munkras算法解决二分图最优权值匹配

  在看这篇博文之前建议看一下上一篇匈牙利法解决二分图最大匹配问题:

  https://www.cnblogs.com/fangxiaoqi/p/10808729.html

  这篇博文参考自:https://www.cnblogs.com/logosG/p/logos.html

最优匹配

  我们这里先说一下什么叫做最优匹配(也被称作最大带权分配)。

  简而言之,最优匹配就是指在带权边的二分图中,求一个匹配使得匹配边上的权值和最大。

  

两个例子

例子1

  出自wenr博客: http://www.cnblogs.com/wenruo/p/5264235.html

  这也是一个拯救单身的例子。红线上的权值表示女生对男生的好感度,至于单身男青年,对于女生的期望为0(有妹子就行,不挑),最优权值匹配即好感度之和最大。

  

  一:将女生的最大期望和男生的期望标出来,开始匹配。

  

  ==========================女1==========================

  女1 X 男1 : 4 + 0 != 3  不匹配;

  女1 X 男2:  4 + 0  = 4  匹配

  成功脱单。

  ==========================女2==========================

  女2 X 男1: 3 + 0 != 2 不匹配;

  女2 X 男2: 3 + 0 != 1 不匹配;

  女2 X 男3: 3 + 0  = 3  匹配;

  但是!男3 已经 名草有主了。

  这时,男3 是个香饽饽,妹纸们都抢着要。男3对女生的期望 +1 ,而为了更好的分配,抢男生的妹子们要对男生的要求要降低一点

  局势就变成这样了:

  

  二:接下来就是一个递归的过程喽(下面的也是)

  ==========================女2==========================

  女2 X 男1: 2 + 0 != 2  匹配;

  成功脱单。

  ==========================女3==========================

  女3 X 男3: 5 + 1 != 5  不匹配;

  然后妹子3只能降低标准了。

  

  ==========================女3==========================

  女3 X 男3: 4 + 1 != 5  匹配;

  但是!男3有了吖。好叭,男3再次增加期望(渣男),妹子3和妹子1把要求降低一点。

  妹子1尝试换人,去和妹子2抢男1。

  但是!男1也有了吖。好叭,男1的期望+1,妹子2的期望降低一点。

  妹子2尝试换人,去和男2匹配。

  成功脱单。

  

  最后,女1 X 男1,女2 X 男2,女3 X 男3 为最优匹配,大团圆结局。

  而对于这种问题我们为什么要用KM算法?这显然是可以看出来的。但是,当单身男女数量越来越多,很难直接看出,而且如果仅仅用匈牙利算法找最大匹配然后比较每个最大匹配的权值来找最优匹配的实行难度也就越来越大,使用KM算法是一种好的选择。从上面的例子我们可以看出,KM算法是基于匈牙利方法进行不断进行递归和贪心的。

例子2

  出自 https://www.cnblogs.com/logosG/p/logos.html

  这是一个公司任免问题。

  在一家公司里,有员工A,B,C,有三种工作a,b,c,如果员工和工作之间有线相连,则代表员工能胜任这份工作,而每个员工做每份工作的效率各不相同,图中的权值代表工作效率,而我们的目的就是做合适的安排保证最大的效率。

  

  标出最大权值和右侧的期望。

  

  ==========================安排A==========================

  A--a: 4 + 0 != 3  不匹配;

  A--c: 4 + 0  = 4     匹配;

  ==========================安排B==========================

  B--a: 3 + 0 != 2  不匹配;

  B--b: 3 + 0 != 1  不匹配;

  B--c: 3 + 0 != 3     匹配;但是产生冲突。

  进行 -1 和 +1 的操作,来波递归。

  

  B--a: 2 + 0 = 2   匹配。

  ==========================安排C==========================

  C--c: 5 + 1 != 5   不匹配;  对C减1

  

  C--c: 4 + 1 = 5   匹配但冲突;(情形类似于例1,递归和贪心即可)

  最终,A--a,B--b,C--c。  

KM算法的思想

  KM算法是对匈牙利算法的一种贪心扩展,而其思想是通过给每个顶点一个标号(叫做顶标)来把求完备匹配(匈牙利算法求出完备匹配)下的最大权匹配问题(即最优分配)。通过修改某些点的符号(但要满足点标始终是可行的),不断增加图中可行边总数,直到图中存在仅由可行边组成的完全匹配为止,此时这个匹配一定是最佳的。

  具体说一下最优匹配——定理:设$M$是一个带权完全二分图$G$的一个完备匹配,给每个顶点一个可行顶标(第$i$个$x$顶点的可行顶标用 $lx[i]$表示,第$j$个$y$顶点的可行顶标用 $ly[j]$ 表示),如果对所有的边$(i,j)$ in G,都有 $lx[i] + ly[j] ge w[i,j]$ 成立($wleft[ {i,j} ight]$ 表示边的权),且对所有的边$(i,j)$ $in$ $M$,都有 $lxleft[ i ight] + lyleft[ j ight] = wleft[ {i,j} ight]$ 成立,则$M$是图$G$的一个最优匹配。

KM算法的步骤

  1. 用邻接矩阵(或其他方法也行啦)来储存图,注意:如果只是想求最大权值匹配而不要求是完全匹配的话,请把各个不相连的边的权值设置为0。
  2. 运用贪心算法初始化标杆。
  3. 运用匈牙利算法找到完备匹配。
  4. 如果找不到,则通过修改标杆,增加一些边。
  5. 重复3,4的步骤,直到完全匹配时可结束。

KM算法的流程

  (1)初始化可行顶标的值 (设定$lx$,$ly$的初始值) 。

  (2)用匈牙利算法寻找相等子图的完备匹配。  

  (3)若未找到增广路则修改可行顶标的值。

  重复(2)(3)直到找到相等子图的完备匹配为止。

参考自:http://www.cnblogs.com/zpfbuaa/p/7218607.html

hdoj 2255 奔小康赚大钱

  我们以HDOJ2255为例。http://acm.hdu.edu.cn/showproblem.php?pid=2255

奔小康赚大钱

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 15718    Accepted Submission(s): 6746

  Problem Description
  传说在遥远的地方有一个非常富裕的村落,有一天,村长决定进行制度改革:重新分配房子。
  这可是一件大事,关系到人民的住房问题啊。村里共有n间房间,刚好有n家老百姓,考虑到每家都要有房住(如果有老百姓没房子住的话,容易引起不安定因素),每家必须分配到一间房子且只能得到一间房子。
  另一方面,村长和另外的村领导希望得到最大的效益,这样村里的机构才会有钱。由于老百姓都比较富裕,他们都能对每一间房子在他们的经济范围内给出一定的价格,比如有3间房子,一家老百姓可以对第一间出10万,对第2间出2万,对第3间出20万(当然是在他们的经济范围内)。.现在这个问题就是村领导怎样分配房子才能使收入最大(村民即使有钱购买一间房子但不一定能买到,要看村领导分配的)?

  Input

  输入数据包含多组测试用例,每组数据的第一行输入n,表示房子的数量(也是老百姓家的数量),接下来有n行,每行n个数表示第 i 个村民对第 j 间房出的价格(n<=300)。
  Output
  请对每组数据输出最大的收入值,每组的输出占一行。
  Sample Input
  2
  100 10
  15 23
  Sample Output
  123
  水题一道,直接套模板即可。
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;

int love[305][305];    // 每个妹子对每个男生的好感度 
int ex_girl[305];      // 每个妹子的期望值
int ex_boy[305];       // 每个男生的期望值
bool vis_girl[305];    // 每一轮匹配匹配过的女生
bool vis_boy[305];     // 每一轮匹配匹配过的男生
int match[305];        // 每个男生匹配到的妹子 如果没有则为-1
int slack[305];        // 每个汉子如果能被妹子倾心最少还需要多少期望值
int n;

bool dfs(int girl){
    vis_girl[girl] = true;
    for (int boy = 0; boy < n; boy++) {
        if (vis_boy[boy]) continue; // 每一轮匹配 每个男生只尝试一次
        int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy];
        if (gap == 0) {  // 如果符合要求
            vis_boy[boy] = true;
            if (match[boy] == -1 || dfs( match[boy] )) {    // 找到一个没有匹配的男生 或者该男生的妹子可以找到其他人
                match[boy] = girl;
                return true;
            }
        }else{
            slack[boy] = min(slack[boy], gap);  // slack 可以理解为该男生要得到女生的倾心 还需多少期望值 取最小值 备胎的样子
        }
    }
    return false;
}
int KM(){
    memset(match, -1, sizeof match);    // 初始每个男生都没有匹配的女生
    memset(ex_boy, 0, sizeof ex_boy);   // 初始每个男生的期望值为0
    // 每个女生的初始期望值是与她相连的男生最大的好感度
    for (int i = 0; i < n; i++) {
        ex_girl[i] = love[i][0];
        for (int j = 1; j < n; j++) {
            ex_girl[i] = max(ex_girl[i], love[i][j]);
        }
    }
    // 尝试为每一个女生解决归宿问题
    for (int i = 0; i < n; i++) {
        fill(slack, slack + n, INF);    // 因为要取最小值 初始化为无穷大
        while(1){
            // 为每个女生解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止
            // 记录每轮匹配中男生女生是否被尝试匹配过
            memset(vis_girl, false, sizeof vis_girl);
            memset(vis_boy, false, sizeof vis_boy);
            if(dfs(i)) break;  // 找到归宿 退出
            // 如果不能找到 就降低期望值
            // 最小可降低的期望值
            int d = INF;
            for (int j = 0; j < n; j++)
                if (!vis_boy[j])    d = min(d, slack[j]);
            for (int j = 0; j < n; j++) {
                // 所有访问过的女生降低期望值
                if (vis_girl[j]) ex_girl[j] -= d;
                // 所有访问过的男生增加期望值
                if (vis_boy[j]) ex_boy[j] += d;
                // 没有访问过的boy 因为girl们的期望值降低,距离得到女生倾心又进了一步!
                else slack[j] -= d;
            }
        }
    }
    // 匹配完成 求出所有配对的好感度的和
    int res = 0;
    for (int i = 0; i < n; i++)
        res += love[match[i]][i];
    return res;
}
int main(){
    while (cin>>n) {
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                cin>>love[i][j];
        cout<<KM()<<endl;
    }
    return 0;
}        
 
 
原文地址:https://www.cnblogs.com/cruelty_angel/p/10810038.html