浅谈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。