KM算法详解+模板
搞笑版:https://www.cnblogs.com/wenruo/p/5264235.html
理论版:https://www.cnblogs.com/zpfbuaa/p/7218607.html#_label3
现在有N男N女,有些男生和女生之间互相有好感,我们将其好感程度定义为好感度,我们希望把他们两两配对,并且最后希望好感度和最大。
怎么选择最优的配对方法呢?
首先,每个女生会有一个期望值,就是与她有好感度的男生中最大的好感度。男生呢,期望值为0,就是……只要有一个妹子就可以啦,不挑~~
这样,我们把每个人的期望值标出来。
接下来,开始配对。
配对方法:
我们从第一个女生开始,分别为每一个女生找对象。
每次都从第一个男生开始,选择一个男生,使男女两人的期望和要等于两人之间的好感度。
注意:每一轮匹配,每个男生只会被尝试匹配一次!
具体匹配过程:
==============为女1找对象===============
(此时无人配对成功)
根据 “男女两人的期望和要等于两人之间的好感度”的规则
女1-男1:4+0 != 3
女1-男3:4+0 == 4
所以女1选择了男3
女1找对象成功
==============为女1找对象成功============
==============为女2找对象===============
(此时女1—男3)
根据配对原则,女2选择男3
男3有主女1,女1尝试换人
我们尝试让女1去找别人
尝试失败
为女2找对象失败!
==============为女2找对象失败============
这一轮参与匹配的人有:女1,女2,男3。
怎么办???很容易想到的,这两个女生只能降低一下期望值了,降低多少呢?
这轮
比如:女1选择男1,期望值要降低1。 女2选择男1,期望值要降低1。 女2选择男2,期望值要降低2。
于是,只要期望值降低1,就有妹子可能选择其他人。所以妹子们的期望值要降低1点。
同时,刚才被抢的男生此时非常得意,因为有妹子来抢他,于是他的期望值提高了1点(就是同妹子们降低的期望值相同)。
于是期望值变成这样(当然,不参与刚才匹配过程的人期望值不变)
==============继续为女2找对象=============
(此时女1—男3)
女2选择了男1
男1还没有被配对
女2找对象成功!
==============为女2找对象成功=============
==============为女3找对象===============
(此时女1—男3,女2-男1)
女3没有可以配对的男生……
女3找对象失败
==============为女3找对象失败============
此轮只有女3参与匹配
此时应该为女3降低期望值
降低期望值1的时候,女3-男3可以配对,所以女3降低期望值1
==============继续为女3找对象============
(此时女1—男3, 女2-男1)
女3相中了男3
此时男3已经有主女1,于是女1尝试换人
女1选择男1
而男1也已经有主女2,女2尝试换人
前面说过,每一轮匹配每个男生只被匹配一次
所以女2换人失败
女3找对象再次失败
==============为女3找对象失败============
这一轮匹配相关人员:女1,女2,女3,男1,男3
此时,只要女2降低1点期望值,就能换到男2
(前面提过 只要任意一个女生能换到任意一个没有被选择过的男生所需要降低的最小值)
我们把相应人员期望值改变一下
==============还是为女3找对象============
(此时女1—男3, 女2-男1)
女3选择了男3
男3有主女1,女1尝试换人
女1换到了男1
男1已经有主女2,女2尝试换人
女2换人男2
男2无主,匹配成功!!!
==============为女3找对象成功=============
匹配成功!!!撒花~~
到此匹配全部结束
此时
女1-男1,女2-男2,女3-男3
好感度和为最大:9
个人整理版
给出如下关系
3
3 0 4
2 1 3
0 0 5
首先将女生的标顶值变设为与之相连的男生的最大边权,男生的为0.于是标顶什如下
序号 女生 男生
1 4 0
2 3 0
3 5 0
然后匹配过程
对于1号女生,其找到3号男生.
对于2号女生,清空先前的标记
她如果找到1号男生,则cnt=lx[2]+ly[1]-value[1,1]=3+0-2=1
她如果找到2号男生,则cnt=lx[2]+ly[2]-value[1,2]=3+0-1=2
她如果找到3号男生,则cnt=lx[2]+ly[3]-value[1,3]=3+0-3=0
于是2号可以去找3号男生,给3号男生打上一个标记
但发现其已许配给了1号女生。于是尝试给1号女生挪动一下
发现1号女生与1号男生并不能匹配成功,但求出来的
lx[1]+ly[1]-value[1,1]=4+0-3=1是最小的
于是修正标顶值
序号 女生 男生
1 3 0
2 2 0
3 5 1
修正完成后,再来为2号女生找
2号女生找到1号男生,发现cnt=lx[2]+ly[1]-value[1,1]=2+0-2=0
并且1号男生没有许配给任何人,所以匹配成功。
总结下:
这两次匹配得到的总权值为6
在第二次匹配中,我们修正的标顶的三个人中,
女1和男3这一对中,一人加一人减总和并不变
女2的标顶值变小,降低了要求,于是顺利找到1号。
对于3号女生,清空先前的标记
3号女生只与3号男生相连,但并不能匹配成功但
cnt=lx[3]+ly[3]-value[3,3]=5+1-5=1
于是3号女生一个人调整调整标顶值为5-1=4。
然后带着期望值4再去找.
这一次找到3号男生,给3号男生打上标记。
但发现3号男已许配给1号女生。于是希望1号女生挪动一下。
1号女生带着期望值3去找,找到1号男生满足条件,给1号打上标记。
但是1号男生许给了2号女生。于是2号女生向后挪动。
2号女生就只能找到2号男生,但匹配不成功,cnt=1
于是对女1,2,3,男1,3进行调整,调整值为1,调整后得到
序号 女生 男生
1 2 1
2 1 0
3 3 2
最后让3号女生带着3的期望去找。找到3号男生
3号男生被1号女生占着,于是1号女生找到1号男生
1号男生被2号女生占着,于是2号女生找到2号男生,匹配全部完成。
虽然不停换人的过程听起来很麻烦,但其实整个是个递归的过程,实现起来比较简单。比较复杂的部分就是期望值的改变,但是可以在递归匹配的过程中顺带求出来。
#include <iostream> #include <cstring> #include <cstdio> using namespace std; const int MAXN = 305; const int INF = 0x3f3f3f3f; int love[MAXN][MAXN]; // 记录每个妹子和每个男生的好感度 int ex_girl[MAXN]; // 每个妹子的期望值 int ex_boy[MAXN]; // 每个男生的期望值 bool vis_girl[MAXN]; // 记录每一轮匹配匹配过的女生 bool vis_boy[MAXN]; // 记录每一轮匹配匹配过的男生 int match[MAXN]; // 记录每个男生匹配到的妹子 如果没有则为-1 int slack[MAXN]; // 记录每个汉子如果能被妹子倾心最少还需要多少期望值 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 (~scanf("%d", &N)) { for (int i = 0; i < N; ++i) for (int j = 0; j < N; ++j) scanf("%d", &love[i][j]); printf("%d\n", KM()); } return 0; }
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=305; const int inf=0x3f3f3f3f; int n; int w[maxn][maxn]; int lx[maxn],ly[maxn]; int matched[maxn]; int slack[maxn]; bool s[maxn],t[maxn]; bool match(int i) { s[i]=1; //第i个女生打上标记 for(int j=1;j<=n;j++) { int cnt=lx[i]+ly[j]-w[i][j]; //求出gap值 if(cnt==0&&!t[j]) //如果gap值为0,且第J个男生在本轮没有被占用 { t[j]=1; //打上标记 if(!matched[j]||match(matched[j])) //如果男生J从来没有被许配,或者被许配的对象可以向后调整的话 { matched[j]=i; //标记男生j被第i个女生占有 return 1; } } else { slack[j]=min(slack[j],cnt); //求出最小的调整值 //调整值只发生在匹配不成功的时候,也就是gap!=0时 } } return 0; } void update() { int a=inf; for(int i=1;i<=n;i++) { if(!t[i]) //求出所有未匹配成功的男生中最小的差距值 a=min(a,slack[i]); } for(int i=1;i<=n;i++) //对女生的顶标值变小,男生的变大,这样保证女生可以找到更多的男生 //也就是能有边进入到我们的相等子图中来 { if(s[i])lx[i]-=a; if(t[i])ly[i]+=a; } } void km() { memset(matched,0,sizeof(matched)); memset(lx,0,sizeof(lx)); memset(ly,0,sizeof(ly)); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { lx[i]=max(lx[i],w[i][j]); } } for(int i=1;i<=n;i++) { memset(slack,0x3f,sizeof(slack)); while(1) //整体采用贪心算法,保证每次能使一个女生匹配成功 { memset(s,0,sizeof(s)); memset(t,0,sizeof(t)); if(match(i)) break; else //调整标顶值 update(); } } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { scanf("%d",&w[i][j]); } } km(); int ans=0; for(int i=1;i<=n;i++) { ans+=lx[i]; ans+=ly[i]; } printf("%d\n",ans); return 0; }
模板(带详细注释)(入门题:HDU2255(复杂度应该是O(N^3)
#include <iostream> #include <cstring> #include <cstdio> using namespace std; const int MAXN = 305; const int INF = 0x3f3f3f3f; int love[MAXN][MAXN]; // 记录每个妹子和每个男生的好感度 int ex_girl[MAXN]; // 每个妹子的期望值 int ex_boy[MAXN]; // 每个男生的期望值 bool vis_girl[MAXN]; // 记录每一轮匹配匹配过的女生 bool vis_boy[MAXN]; // 记录每一轮匹配匹配过的男生 int match[MAXN]; // 记录每个男生匹配到的妹子 如果没有则为-1 int slack[MAXN]; // 记录每个汉子如果能被妹子倾心最少还需要多少期望值 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 (~scanf("%d", &N)) { for (int i = 0; i < N; ++i) for (int j = 0; j < N; ++j) scanf("%d", &love[i][j]); printf("%d\n", KM()); } return 0; }
ural1076 Trash 垃圾
你受聘于当地的垃圾处理公司任CEO,你的一项工作是处理引进的垃圾,以及分类垃圾以进行循环利用。每天,垃圾会有几个集装箱运来,每一个集装箱都包含几种垃圾。给定集装箱里垃圾的数目,请找出最佳的途径去分类这些垃圾。分类垃圾即把每种垃圾分开装到不同的集装箱中。每个集装箱的容量都是无限的。搬动一个单位的垃圾需要耗费代价,从集装箱i到j是1(i<>j,否则代价为0),你必须把代价减到最小。
Input
第一行为n (1 <= n <= 150),以下为集装箱的情况。
第i+1行为第i个集装箱中的j种垃圾的数量amount(0 <= amount <= 100)。
Output
分类这些垃圾的所需的最小代价。
Sample Input
4
62 41 86 94
73 58 11 12
69 93 89 88
81 40 69 13
Sample Output
650
#include<stdio.h> #include<string.h> #include<stdlib.h> #include <iostream> using namespace std; #define MAX 302 int N; const int inf=10000000; bool x[MAX],y[MAX]; int map[MAX][MAX]; int my[MAX]; int lx[MAX],ly[MAX]; int slack[MAX]; bool dfs(int t){ int u; x[t]=1; for(u=1;u<=N;u++){ int wt=lx[t]+ly[u]-map[t][u]; if(!y[u]&&wt==0){ y[u]=1; if(my[u]==-1||dfs(my[u])){ my[u]=t; return 1; } } else if(slack[u]>wt) slack[u]=wt; } return 0; } int perfect_match(){ int i,j,k; for(i=1;i<=N;i++){ my[i]=-1; lx[i]=-inf,ly[i]=0; for(j=1;j<=N;j++){ if(lx[i]<map[i][j]) lx[i]=map[i][j]; } } for(k=1;k<=N;k++){ memset(x,0,sizeof(x)); memset(y,0,sizeof(y)); for(i=1;i<=N;i++) slack[i]=inf; while(!dfs(k)){ int d=inf; for(i=1;i<=N;i++){ if(!y[i]&&slack[i]<d){d=slack[i];} } for(i=1;i<=N;i++){ if(x[i]){ lx[i]=lx[i]-d; x[i]=0; } if(y[i]){ ly[i]=ly[i]+d; y[i]=0; } } } } int ans=0; for(i=1;i<=N;i++) ans+=(lx[i]+ly[i]); return ans; } int main(){ int cost; scanf("%d",&N); for(int i=1;i<=N;i++){ cost=0; for(int j=1;j<=N;j++){ scanf("%d",&map[i][j]); cost+=map[i][j]; } for(int j=1;j<=N;j++) map[i][j]=cost-map[i][j]; } for(int i=1;i<=N;i++) for(int j=1;j<=N;j++) map[i][j]=-map[i][j]; cout<<-perfect_match()<<endl; return 0; }