编程语言
首页 > 编程语言> > 【luogu P6577】【模板】二分图最大权完美匹配(KM算法)

【luogu P6577】【模板】二分图最大权完美匹配(KM算法)

作者:互联网

【模板】二分图最大权完美匹配

题目链接:luogu P6577

题目大意

一个二分图,有一些带权边,保证有完美匹配。
求一种最大匹配的方案使得匹配边的边权和最大。

思路

KM 算法的模板题。

它有一定的针对性:一定要是带权的完美匹配。
然后我们定义每个点有一个顶表(一个值)\(e_x\)。

对于每一条边 \((u,v)\),我们要满足 \(e_u+e_v\geqslant w(u,v)\)。
然后如果 \(e_u+e_v=w(u,v)\),那我们就可以把这条边放进二分图中。
那如果这个时候的图能跑出完美匹配,那此时的边权和即是结果。

但是你发现定标的值不能直接确定,所以算法的流程是这样的:
确定定标的值,跑匹配判断(如果匹配是 \(n\) 就结束),否则就修改定标,然后重复操作。

然后接着问题就是一开始怎么给定标的值,而且每次怎么修改。
一开始给定标,我们可以让 \(ex_x=0,ey_x=\max\limits_{i=1}^nw(x,i)\)。
然后每次修改,我们就是要减少一些 \(e_u+e_v\) 的值使得更多边在图中。

那我们修改就是找一条边 \((i,j)\),它一个不在最大匹配,一个在。
那我们要让他加入,我们就要让他满足条件,那定标和要减少 \(d_i+d_j-w(i,j)\)。
那因为 \(j\) 已经在最大匹配中了,所以我们就直接把二分图最大匹配中的任意点 \(i\) 都把 \(ex_i+d\) 或者把 \(ey_i-d\)。

那我们为了满足 \(e_u+e_v\geqslant w(u,v)\),所以我们要每次的 \(d\) 尽量小。
那每次找边复杂度 \(O(n^2)\),二分图匹配的复杂度是 \(O(n^2)\),总的复杂度为 \(O(n^4)\)。

然后发现每次都暴力找 \(d\) 太慢了,我们考虑用一个数组 \(slack_j\) 表示 \(ex_i+ey_i-w(i,j)\) 的最小值,然后在跑增广路的时候修改即可做到 \(O(n^3)\)。

吗?
其实会假。因为如果你匹配的部分跑到 \(O(n^2)\),它还是 \(O(n^4)\)。
然后我们发现我们每次只是修改了一条边,所以我们匹配的时候有一部分是跟原来一样的。
然后我们把 dfs 改成 bfs,就可以真正的变成 \(O(n^3)\)。

代码

\(O(n^4)\) 版

#include<cstdio>
#include<iostream>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

int n, m, x, y, matched[501];
ll dis[501][501], ex[501], ey[501], slack[501], w;
bool inx[501], iny[501];

bool match(int now) {
	iny[now] = 1;
	for (int i = 1; i <= n; i++) {
		if (inx[i]) continue;
		ll g = ex[i] + ey[now] - dis[now][i];
		if (!g) {
			inx[i] = 1;
			if (!matched[i] || match(matched[i])) {
				matched[i] = now; return 1;
			}
		}
		else slack[i] = min(slack[i], g);
	}
	return 0;
}

ll KM() {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) slack[j] = INF;
		while (1) {
			for (int j = 1; j <= n; j++) inx[j] = iny[j] = 0;
			if (match(i)) break;
			ll d = INF;
			for (int j = 1; j <= n; j++) {
				if (!inx[j]) d = min(d, slack[j]);
			}
			for (int j = 1; j <= n; j++) {
				if (iny[j]) ey[j] -= d;
				if (inx[j]) ex[j] += d;
					else slack[j] -= d;
			}
		}
	}
	ll re = 0;
	for (int i = 1; i <= n; i++)	
		re += dis[matched[i]][i];
	return re;
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
		ey[i] = -INF;
		for (int j = 1; j <= n; j++)
			dis[i][j] = -INF;
	}
	for (int i = 1; i <= m; i++) {
		scanf("%d %d", &x, &y); scanf("%lld", &w);
		dis[x][y] = max(dis[x][y], w);
		ey[x] = max(ey[x], dis[x][y]);
	}
	
	printf("%lld\n", KM());
	for (int i = 1; i <= n; i++)
		printf("%d ", matched[i]);
	
	return 0;
}

\(O(n^3)\) 版

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

int n, m, x, y, matched[501], bef[501];
ll dis[501][501], ex[501], ey[501], slack[501], w;
bool iny[501];

void match(int now) {
	int x, y = 0, ty = 0;
	matched[y] = now;
	while (1) {
		x = matched[y]; ll d = INF; iny[y] = 1;
		for (int i = 1; i <= n; i++) {
			if (iny[i]) continue;
			if (slack[i] > ex[x] + ey[i] - dis[x][i]) {
				slack[i] = ex[x] + ey[i] - dis[x][i];
				bef[i] = y;
			}
			if (slack[i] < d) {
				d = slack[i]; ty = i;
			}
		}
		for (int i = 0; i <= n; i++) {
			if (iny[i]) ex[matched[i]] -= d, ey[i] += d;
				else slack[i] -= d;
		}
		y = ty;
		if (!matched[y]) break;
	}
	while (y) {
		matched[y] = matched[bef[y]];
		y = bef[y];
	}
}

ll KM() {
	for (int i = 1; i <= n; i++) {
		memset(iny, 0, sizeof(iny));
		for (int j = 1; j <= n; j++) slack[j] = INF;
		memset(bef, 0, sizeof(bef));
		match(i);
	}
	ll re = 0;
	for (int i = 1; i <= n; i++)	
		if (matched[i])
			re += dis[matched[i]][i];
	return re;
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
		ey[i] = -INF;
		for (int j = 1; j <= n; j++)
			dis[i][j] = -INF;
	}
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %lld", &x, &y, &w);
		dis[x][y] = max(dis[x][y], w);
		ey[x] = max(ey[x], dis[x][y]);
	}
	
	printf("%lld\n", KM());
	for (int i = 1; i <= n; i++)
		printf("%d ", matched[i]);
	
	return 0;
}

标签:匹配,slack,int,luogu,KM,P6577,ex,ey,501
来源: https://www.cnblogs.com/Sakura-TJH/p/luogu_P6577.html