作者:logosG

链接:https://www.cnblogs.com/logosG/p/logos.html (讲解的KM算法,特别厉害!!!)

KM算法:

现在我们来考虑另外一个问题:如果每个员工做每件工作的效率各不相同,我们如何得到一个最优匹配使得整个公司的工作效率最大呢?

这种问题被称为带权二分图的最优匹配问题,可由KM算法解决。

比如上图,A做工作a的效率为3,做工作c的效率为4......以此类推。

不了解KM算法的人如何解决这个问题?我们只需要用匈牙利算法找到所有的最大匹配,比较每个最大匹配的权重,再选出最大权重的最优匹配即可。这不失为一个解决方案,但是,如果公司员工的数量越来越多,此种算法的实行难度也就越来越大,我们必须另辟蹊径:KM算法。

KM算法解决此题的步骤如下所示:

1.首先对每个顶点赋值,将左边的顶点赋值为最大权重,右边的顶点赋值为0。

如图,我们将顶点A赋值为其两边中较大的4。

2.进行匹配,我们匹配的原则是:只与权重相同的边匹配,若是找不到边匹配,对此条路径的所有左边顶点-1,右边顶点+1,再进行匹配,若还是匹配不到,重复+1和-1操作。(这里看不懂可以跳过,直接看下面的操作,之后再回头来看这里。)

对A进行匹配,符合匹配条件的边只有Ac边。

匹配成功!

接下来我们对B进行匹配,顶点B值为3,Bc边权重为3,匹配成~ 等等,A已经匹配c了,发生了冲突,怎么办?我们这时候第一时间应该想到的是,让B换个工作,但根据匹配原则,只有Bc边 3+0=0 满足要求,于是B不能换边了,那A能不能换边呢?对A来说,也是只有Ac边满足4+0=4的要求,于是A也不能换边,走投无路了,怎么办?

从常识的角度思考:其实我们寻找最优匹配的过程,也就是帮每个员工找到他们工作效率最高的工作,但是,有些工作会冲突,比如现在,B员工和A员工工作c的效率都是最高,这时我们应该让A或者B换一份工作,但是这时候换工作的话我们只能换到降低总体效率值的工作,也就是说,如果令R=左边顶点所有值相加,若发生了冲突,则最终工作效率一定小于R,但是,我们现在只要求最优匹配,所以,如果A换一份工作降低的工作效率比较少的话,我们是能接受的(对B同样如此)。

在KM算法中如何体现呢?

现在参与到这个冲突的顶点是A,B和c,令所有左边顶点值-1,右边顶点值+1,即 A-1,B-1. c+1,结果如下图所示。

我们进行了上述操作后会发现,若是左边有n个顶点参与运算,则右边就有n-1个顶点参与运算,整体效率值下降了1*(n-(n-1))=1,而对于A来说,Ac本来为可匹配的边,现在仍为可匹配边(3+1=4),对于B来说,Bc本来为可匹配的边,现在仍为可匹配的边(2+1=3),我们通过上述操作,为A增加了一条可匹配的边Aa,为B增加了一条可匹配的边Ba。

现在我们再来匹配,对B来说,Ba边 2+0=2,满足条件,所以B换边,a现在为未匹配状态,Ba匹配!

我们现在匹配最后一条边C,Cc 5+1!=5,C边无边能匹配,所以C-1。

现在Cc边 4+1=5,可以匹配,但是c已匹配了,发生冲突,C此时不能换边,于是便去找A,对于A来说,Aa此时也为可匹配边,但是a已匹配,A又去找B。

B现在无边可以匹配了,2+0!=1 ,现在的路径是C→c→A→a→B,所以A-1,B-1,C-1,a+1,c+1。如下图所示。

对于B来说,现在Bb 1+0=1 可匹配!

使用匈牙利算法,对此条路径上的边取反。

如图,便完成了此题的最优匹配。

读者可以发现,这题中冲突一共发生了3次,所以我们一共降低了3次效率值,但是我们每次降低的效率值都是最少的,所以我们完成的仍然是最优匹配

这就是KM算法的整个过程,整体思路就是:每次都帮一个顶点匹配最大权重边,利用匈牙利算法完成最大匹配,最终我们完成的就是最优匹配

下面便根据HDU 2255 奔小康赚大钱 给出KM代码:

题意:

n间房子,n个人。每一个人对每一间房子有自己的估价,他们会按照自己的估价买房子。现在你需要将每一间房子分给每一个人,这些人会按照自己的估价给你钱。你需要让最后得到的总钱数最大

题解:

很显然就是一道最大权匹配

代码:

 1 #include<stdio.h>
2 #include<algorithm>
3 #include<string.h>
4 #include<iostream>
5 #include<queue>
6 #include<vector>
7 using namespace std;
8 const int maxn=510;
9 const int INF=0x3f3f3f3f;
10 int n;
11 int w[maxn][maxn],link[maxn],matchx[maxn],matchy[maxn];
12 int visitx[maxn],visity[maxn];
13 int sum[maxn];
14 int dfs(int x)
15 {
16 visitx[x]=1;
17 for(int i=1;i<=n;++i)
18 {
19 if(visity[i]) continue;
20 int temp=matchx[x]+matchy[i]-w[x][i];
21 if(!temp)
22 {
23 visity[i]=1;
24 if(link[i]==-1 || dfs(link[i]))
25 {
26 link[i]=x;
27 return 1;
28 }
29 }
30 else if(sum[i]>temp)
31 sum[i]=temp;
32 }
33 return 0;
34 }
35 int km()
36 {
37 memset(link,-1,sizeof(link));
38 memset(matchy,0,sizeof(matchy));
39 for(int i=1;i<=n;++i)
40 {
41 matchx[i]=-INF;
42 for(int j=1;j<=n;++j)
43 {
44 if(w[i][j]>matchx[i])
45 matchx[i]=w[i][j];
46 }
47 }
48 for(int x=1;x<=n;++x)
49 {
50 for(int i=1;i<=n;++i)
51 {
52 sum[i]=INF;
53 }
54 while(1)
55 {
56 memset(visitx,0,sizeof(visitx));
57 memset(visity,0,sizeof(visity));
58 if(dfs(x)) break;
59 int d=INF;
60 for(int i=1;i<=n;++i)
61 {
62 if(!visity[i] && d>sum[i])
63 d=sum[i];
64 }
65 for(int i=1;i<=n;++i)
66 {
67 if(visitx[i])
68 matchx[i]-=d;
69 }
70 for(int i=1;i<=n;++i)
71 {
72 if(visity[i]) matchy[i]+=d;
73 else sum[i]-=d;
74 }
75 }
76 }
77 int ans=0;
78 for(int i=1;i<=n;++i)
79 {
80 if(link[i]!=-1)
81 ans+=w[link[i]][i];
82 }
83 return ans;
84 }
85 int main()
86 {
87 while(~scanf("%d",&n))
88 {
89 for(int i=1;i<=n;++i)
90 {
91 for(int j=1;j<=n;++j)
92 {
93 scanf("%d",&w[i][j]);
94 }
95 }
96 printf("%d\n",km());
97 }
98 return 0;
99 }

带注释的代码:

  1 #include <iostream>
2
3 #include <cstring>
4
5 #include <cstdio>
6
7
8
9 using namespace std;
10
11 const int MAXN = 305;
12
13 const int INF = 0x3f3f3f3f;
14
15
16
17 int love[MAXN][MAXN]; // 记录每个妹子和每个男生的好感度
18
19 int ex_girl[MAXN]; // 每个妹子的期望值
20
21 int ex_boy[MAXN]; // 每个男生的期望值
22
23 bool vis_girl[MAXN]; // 记录每一轮匹配匹配过的女生
24
25 bool vis_boy[MAXN]; // 记录每一轮匹配匹配过的男生
26
27 int match[MAXN]; // 记录每个男生匹配到的妹子 如果没有则为-1
28
29 int slack[MAXN]; // 记录每个汉子如果能被妹子倾心最少还需要多少期望值
30
31
32
33 int N;
34
35
36
37
38
39 bool dfs(int girl)
40
41 {
42
43 vis_girl[girl] = true;
44
45
46
47 for (int boy = 0; boy < N; ++boy) {
48
49
50
51 if (vis_boy[boy]) continue; // 每一轮匹配 每个男生只尝试一次
52
53
54
55 int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy];
56
57
58
59 if (gap == 0) { // 如果符合要求
60
61 vis_boy[boy] = true;
62
63 if (match[boy] == -1 || dfs( match[boy] )) { // 找到一个没有匹配的男生 或者该男生的妹子可以找到其他人
64
65 match[boy] = girl;
66
67 return true;
68
69 }
70
71 } else {
72
73 slack[boy] = min(slack[boy], gap); // slack 可以理解为该男生要得到女生的倾心 还需多少期望值 取最小值 备胎的样子【捂脸
74
75 }
76
77 }
78
79
80
81 return false;
82
83 }
84
85
86
87 int KM()
88
89 {
90
91 memset(match, -1, sizeof match); // 初始每个男生都没有匹配的女生
92
93 memset(ex_boy, 0, sizeof ex_boy); // 初始每个男生的期望值为0
94
95
96
97 // 每个女生的初始期望值是与她相连的男生最大的好感度
98
99 for (int i = 0; i < N; ++i) {
100
101 ex_girl[i] = love[i][0];
102
103 for (int j = 1; j < N; ++j) {
104
105 ex_girl[i] = max(ex_girl[i], love[i][j]);
106
107 }
108
109 }
110
111
112
113 // 尝试为每一个女生解决归宿问题
114
115 for (int i = 0; i < N; ++i) {
116
117
118
119 fill(slack, slack + N, INF); // 因为要取最小值 初始化为无穷大
120
121
122
123 while (1) {
124
125 // 为每个女生解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止
126
127
128
129 // 记录每轮匹配中男生女生是否被尝试匹配过
130
131 memset(vis_girl, false, sizeof vis_girl);
132
133 memset(vis_boy, false, sizeof vis_boy);
134
135
136
137 if (dfs(i)) break; // 找到归宿 退出
138
139
140
141 // 如果不能找到 就降低期望值
142
143 // 最小可降低的期望值
144
145 int d = INF;
146
147 for (int j = 0; j < N; ++j)
148
149 if (!vis_boy[j]) d = min(d, slack[j]);
150
151
152
153 for (int j = 0; j < N; ++j) {
154
155 // 所有访问过的女生降低期望值
156
157 if (vis_girl[j]) ex_girl[j] -= d;
158
159
160
161 // 所有访问过的男生增加期望值
162
163 if (vis_boy[j]) ex_boy[j] += d;
164
165 // 没有访问过的boy 因为girl们的期望值降低,距离得到女生倾心又进了一步!
166
167 else slack[j] -= d;
168
169 }
170
171 }
172
173 }
174
175
176
177 // 匹配完成 求出所有配对的好感度的和
178
179 int res = 0;
180
181 for (int i = 0; i < N; ++i)
182
183 res += love[ match[i] ][i];
184
185
186
187 return res;
188
189 }
190
191
192
193 int main()
194
195 {
196
197 while (~scanf("%d", &N)) {
198
199
200
201 for (int i = 0; i < N; ++i)
202
203 for (int j = 0; j < N; ++j)
204
205 scanf("%d", &love[i][j]);
206
207
208
209 printf("%d\n", KM());
210
211 }
212
213 return 0;
214
215 }

二分图最大权匹配问题&&KM算法讲解 && HDU 2255 奔小康赚大钱的更多相关文章

  1. HDU 2255 奔小康赚大钱(带权二分图最大匹配)

    HDU 2255 奔小康赚大钱(带权二分图最大匹配) Description 传说在遥远的地方有一个非常富裕的村落,有一天,村长决定进行制度改革:重新分配房子. 这可是一件大事,关系到人民的住房问题啊 ...

  2. [ACM] HDU 2255 奔小康赚大钱 (二分图最大权匹配,KM算法)

    奔小康赚大钱 Problem Description 传说在遥远的地方有一个很富裕的村落,有一天,村长决定进行制度改革:又一次分配房子. 这但是一件大事,关系到人民的住房问题啊. 村里共同拥有n间房间 ...

  3. HDU 2255 奔小康赚大钱 (KM算法 模板题)

    奔小康赚大钱 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Subm ...

  4. HDU 2255.奔小康赚大钱 最大权匹配

    奔小康赚大钱 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Subm ...

  5. HDU 2255 ——奔小康赚大钱——————【KM算法裸题】

    奔小康赚大钱 Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Statu ...

  6. hdu 2255 奔小康赚大钱 最大权匹配KM

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2255 传说在遥远的地方有一个非常富裕的村落,有一天,村长决定进行制度改革:重新分配房子.这可是一件大事 ...

  7. hdu 2255 奔小康赚大钱--KM算法模板

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2255 题意:有N个人跟N个房子,每个人跟房子都有一定的距离,现在要让这N个人全部回到N个房子里面去,要 ...

  8. HDU 2255 奔小康赚大钱 KM算法的简单解释

    KM算法一般用来寻找二分图的最优匹配. 步骤: 1.初始化可行标杆 2.对新加入的点用匈牙利算法进行判断 3.若无法加入新编,修改可行标杆 4.重复2.3操作直到找到相等子图的完全匹配. 各步骤简述: ...

  9. hdu 2255奔小康赚大钱 KM算法模板

    题目链接:http://acm.hdu.edu.cn/showproblem.php? pid=2255 一,KM算法:(借助这个题写一下个人对km的理解与km模板) KM算法主要是用来求解图的最优匹 ...

随机推荐

  1. 【Java】运算符(算术、赋值、比较(关系)、逻辑、条件、位运算符)

    运算符 文章目录 运算符 1. 算术运算符 2. 赋值运算符 3. 比较运算符 4. 逻辑运算符 5. 条件运算符 6. 位运算符 7. 运算符优先级 8. 运算符操作数类型说明 9.code 算术运 ...

  2. 【Vue】Vue框架常用知识点 Vue的模板语法、计算属性与侦听器、条件渲染、列表渲染、Class与Style绑定介绍与基本的用法

    Vue框架常用知识点 文章目录 Vue框架常用知识点 知识点解释 第一个vue应用 模板语法 计算属性与侦听器 条件渲染.列表渲染.Class与Style绑定 知识点解释 vue框架知识体系 [1]基 ...

  3. 【设计模式】Java设计模式精讲之原型模式

    简单记录 - 慕课网 Java设计模式精讲 Debug方式+内存分析 & 设计模式之禅-秦小波 文章目录 1.原型模式的定义 原型-定义 原型-类型 2.原型模式的实现 原型模式的通用类图 原 ...

  4. P2327 [SCOI2005]扫雷(递推)

    题目链接: https://www.luogu.org/problemnew/show/P2327 题目描述 相信大家都玩过扫雷的游戏.那是在一个$n*m$的矩阵里面有一些雷,要你根据一些信息找出雷来 ...

  5. RWCTF2020 DBaaSadge 复现

    数据库题目 2020RWCTF DBaaSadge WP 这是一个很有意思的题目,难到让我绝望,跟着大佬smity的思路跑一下,求大佬抱抱. https://mp.weixin.qq.com/s/jv ...

  6. 上海某小公司面试题:synchronized锁原理

    synchronized锁是Java面试的过程中比较常考的知识点了,从偏向锁->轻量级锁->重量级锁都可以聊 CAS在这篇没有讲述,因为在上一篇已经写了,有兴趣的同学可以翻翻开 目前已经连 ...

  7. uni-app开发经验分享二十一: 图片滑动解锁插件制作解析

    在开发用户模块的时候,相信大家都碰到过一个功能,图片滑动解锁后发送验证码,这里分享我用uni-app制作的一个小控件 效果如下: 需要如下图片资源 template <template> ...

  8. Flink的状态与容错

    本文主要运行到Flink以下内容 检查点机制(CheckPoint) 状态管理器(StateBackend) 状态周期(StateTtlConfig) 关系 首先要将state和checkpoint概 ...

  9. 温习数据算法—js滑块验证码

    前言 大多数的应用软件都需要输入一些验证码,验证码的样式也多种多样. 比如抢票,提交订单需要验证码,很多人就纳闷了,怎么还需要验证码呢?这不是浪费时间嘛. 存在即合理,合理就是现实的. 源码下载地址+ ...

  10. windows 系统 MySQL_5.6.21安装教程

      1.双击安装文件 mysql_installer_community_V5.6.21.1_setup.1418020972.msi,等待安装界面出现,见下图: 2.勾选:I accept thel ...