2193. 分配问题(网络流,费用流,二分图最优匹配)

文章讨论了如何通过二分图模型和最大费用最大流算法解决n人分配n个工作的效益优化问题,涉及SPFA算法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

活动 - AcWing

有 n 件工作要分配给 n 个人做。

第 i 个人做第 j 件工作产生的效益为 cij。

试设计一个将 n 件工作分配给 n 个人做的分配方案。

对于给定的 n 件工作和 n 个人,计算最优分配方案和最差分配方案。

输入格式

第 1 行有 1 个正整数 n,表示有 n 件工作要分配给 n 个人做。

接下来的 n 行中,每行有 n 个整数 cij,表示第 i 个人做第 j 件工作产生的效益为 cij。

输出格式

第一行输出最差分配方案下的最小总效益。

第二行输出最优分配方案下的最大总效益。

数据范围

1≤n≤50
0≤cij≤100

输入样例:
5
2 2 2 1 2
2 3 1 2 4
2 0 1 1 1
2 3 4 3 3
3 2 1 2 1
输出样例:
5
14

解析: 

有 n 个人和 n 件工作,每个人做每个工作都有不同的效率,将 n 个工作分配给 n个人,问我们可以获得的最大效率和最小效率。

我们可以将 n 个人看作左部节点,n 个工作看作右部节点,这就是一个二分图。将每个人做对应工作的效率看作两个节点之间的边的权值,那么其实就是要我们求一个二分图的匹配,使得选中的所有边的权值和最大,这就是一个二分图最优匹配问题。

如果每条边上没有权值,单单是一个二分图最大匹配问题就非常简单,从源点向左部节点连边,右部节点向汇点连边,每个左部节点向每个右部节点连边,所有边的容量都是 1。这样的问题非常直观且常见,因此这里就不再证明原题的方案和流网络的可行流是一一对应的。

可以发现,任意一个可行方案都保证每个员工都能匹配一个工作,意味着任意一个可行方案对应到流网络中都是一个满流,即整数值最大流。

现在再加上每条边上有权值(效率),那么本题就是要我们在所有整数值最大流中找出权值和最大的一个流,就是最大费用最大流。

最大费用最大流只需要在 spfa 算法求最长增广路即可,最长路要求不能有正环,而二分图中不存在正环,因此不会有问题。

另外还要求最小费用最大流,不用再写一遍用 spfa 算法求最短增广路的 EK 算法,可以将所有边的费用取反,这样新图的最大费用最大流再取反回来就是原图的最小费用最大流。

可以先求最小费用最大流,然后将所有边的费用取反去求最大费用最大流。

检验原问题解的集合与可行流的集合一一对应: 

角度一:原问题的每一个解对应一个可行流 

角度二:每一个可行流对应原问题的每一个解 

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
#include<sstream>
#include<deque>
#include<unordered_map>
#include<unordered_set>
#include<bitset>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
const int N = 2500 + 10, M = (2500+100) * 2 + 10, INF = 0x3f3f3f3f;
int n, m, S, T;
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];

void add(int a, int b, int c, int d) {
	e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx++;
	e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx++;
}

bool spfa() {
	int hh = 0, tt = 1;
	memset(d, 0x3f, sizeof d);
	memset(incf, 0, sizeof incf);
	q[0] = S, d[S] = 0, incf[S] = 0x3f;
	while (hh != tt) {
		int t = q[hh++];
		if (hh == N)hh = 0;
		st[t] = 0;
		for (int i = h[t]; i != -1; i = ne[i]) {
			int j = e[i];
			if (f[i] && d[j] > d[t] + w[i]) {
				d[j] = d[t] + w[i];
				incf[j] = min(incf[t], f[i]);
				pre[j] = i;
				if (!st[j]) {
					st[j] = 1;
					q[tt++] = j;
					if (tt == N)tt = 0;
				}
			}
		}
	}
	return incf[T] > 0;
}

int EK() {
	int cost = 0;
	while (spfa()) {
		int t = incf[T];
		cost += t*d[T];
		for (int i = T; i != S; i = e[pre[i] ^ 1]) {
			f[pre[i]] -= t, f[pre[i] ^ 1] += t;
		}
	}
	return cost;
}

int main() {
	cin >> n;
	memset(h, -1, sizeof h);
	S = 0, T = n * n + 1;
	for (int i = 1; i <= n; i++) {
		for (int j = 1,a; j <= n; j++) {
			scanf("%d", &a);
			add(i, j + n, 1, a);
		}
	}
	for (int i = 1; i <= n; i++) {
		add(S, i, 1, 0);
		add(i + n, T, 1, 0);
	}
	printf("%d\n", EK());
	for (int i = 0; i < idx; i += 2) {
		f[i] += f[i ^ 1], f[i ^ 1] = 0;
		w[i] = -w[i], w[i ^ 1] = -w[i ^ 1];
	}
	printf("%d\n", -EK());
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值