961. 最大获利(网络流,最小割,最大权闭合图)#困难

活动 - AcWing

新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是挑战。

THU 集团旗下的 CS&T 通讯公司在新一代通讯技术血战的前夜,需要做太多的准备工作,仅就站址选择一项,就需要完成前期市场研究、站址勘测、最优化等项目。

在前期市场调查和站址勘测之后,公司得到了一共 N 个可以作为通讯信号中转站的地址,而由于这些地址的地理位置差异,在不同的地方建造通讯中转站需 要投入的成本也是不一样的,所幸在前期调查之后这些都是已知数据:

建立第 i 个通讯中转站需要的成本为 Pi(1≤i≤N)。

另外公司调查得出了所有期望中的用户群,一共 M 个。

关于第 i 个用户群的信息概括为 Ai,Bi 和 Ci:这些用户会使用中转站 Ai 和中转站 Bi 进行通讯,公司可以获益 Ci。(1≤i≤M,1≤Ai,Bi≤N)

THU 集团的 CS&T 公司可以有选择的建立一些中转站(投入成本),为一些用户提供服务并获得收益(获益之和)。

那么如何选择最终建立的中转站才能让公司的净获利最大呢?(净获利 = 获益之和 – 投入成本之和)

输入格式

第一行有两个正整数 N 和 M。

第二行中有 N 个整数描述每一个通讯中转站的建立成本,依次为 P1,P2,…,PN 。

以下 M 行,第 (i+2) 行的三个数 Ai,Bi 和 Ci 描述第 i 个用户群的信息。

所有变量的含义可以参见题目描述。

输出格式

输出一个整数,表示公司可以得到的最大净获利。

数据范围

1≤N≤5000,
1≤M≤50000,
0≤Ci,Pi≤100

输入样例:
5 5
1 2 3 4 5
1 2 3
2 3 4
1 3 3
1 4 2
4 5 3
输出样例:
4
样例解释

选择建立 1、2、31、2、3 号中转站,则需要投入成本 66,获利为 1010,因此得到最大收益 44。

解析: 最大权闭合图或最大密度子图
 

有若干个中转站,建立每个中转站都需要花费对应的成本。我们还有若干个用户群,每个用户群连接两个中转站,我们要向满足某个用户群的需求,就必须建立对应的两个中转站,因此我们可以从用户群向对应的两个中转站分别连一条有向边,表示如果想满足这个用户群的需求就必须要两个中转站建立起来。而满足每个用户群的需求都能获得对应的收益。

本题要求最大获利,获利就是总收益减去总成本。可以发现成本都是要减去的,收益都是要增加的。因此如果将所有中转站和用户群看作节点,那么所有中转站的权值就是负的,所有用户群的权值就是正的,选取任何一个用户群,都要算上对应的两个中转站,即从该用户群出发能到的所有点都需要选上,因此在选点的时候其实就是在选闭合点集,而我们的目标是使选的所有点的权值和最大,其实就是让我们求整张图的最大权闭合子图。

因此本题只需要按照求最大权闭合子图的步骤去求即可,首先是建图,从源点向所有正权点连边,容量就是权值;从所有负权点向汇点连边,容量就是权值的绝对值;所有用户群向需要建造的中转站连边,容量是 +∞

 

 最大权闭合图相关概念(作者:小小_88 ):最大权闭合子图的基本概念 - AcWing

#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 = 55e3 + 5, M = (5e4*3 + 5e3) * 2 + 10, INF = 0x3f3f3f3f;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];

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

bool bfs() {
	int hh = 0, tt = 0;
	memset(d, -1, sizeof d);
	q[0] = S, d[S] = 0, cur[S] = h[S];
	while (hh <= tt) {
		int t = q[hh++];
		for (int i = h[t]; i != -1; i = ne[i]) {
			int j = e[i];
			if (d[j] == -1 && f[i]) {
				d[j] = d[t] + 1;
				cur[j] = h[j];
				if (j == T)return 1;
				q[++tt] = j;
			}
		}
	}
	return 0;
}

int find(int u, int limit) {
	if (u == T)return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
		int j = e[i];
		cur[u] = i;
		if (d[j] == d[u] + 1 && f[i]) {
			int t = find(j, min(f[i], limit - flow));
			if (!t)d[j] = -1;
			f[i] -= t, f[i ^ 1] += t, flow += t;
		}
	}
	return flow;
}

int dinic() {
	int ret = 0, flow;
	while (bfs())while (flow = find(S, INF))ret += flow;
	return ret;
}

int main() {
	cin >> n >> m;
	memset(h, -1, sizeof h);
	S = 0, T = n + m + 1;
	for (int i = 1,a; i <= n; i++) {
		scanf("%d", &a);
		add(i + m, T, a);
	}
	int tot = 0;
	for (int i = 1,a,b,c; i <= m; i++) {
		scanf("%d%d%d", &a, &b, &c);
		add(S, i, c);
		add(i, a + m, INF);
		add(i, b + m, INF);
		tot += c;
	}
	printf("%d\n", tot - dinic());
	return 0;
}

本题还可以使用最大密度子图模型处理

之前我们将每个用户群看作一个点连向两个中转站,表示满足每个用户群都需要建造对应的两个中转站,这样使得本题和最大权闭合子图产生联系。

现在我们要将本题和最大密度子图产生联系,由于密度子图的限制要求要想选某条边就必须要选该边对应的两个点,因此我们可以将用户群看作边,要想选某条边就必须选上这条边的两个点。

这就变成了一个密度子图的问题,然后本题是带点权和边权的,点权是负的,边权是正的,这里我们要最大化一个 |E′|+|V′|,而在证明时我们求的是最大化 |E′|+|V′|−g⋅|V′|,我们只需要让 g=0,就能求出本题的答案,所以我们用证明时的建图方式跑一遍最小割,答案就是 (U⋅n−C[S,T])/ 2,由于本题的 g 是固定的,是一种特殊情况,具体的说可以算是一个局部最大密度子图,因此少一步二分,实现起来相对简单一点。

关于密度子图的详细讲解:2324. 生活的艰辛(网络流,最小割,最大密度子图)#困难,重点难点-CSDN博客

#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 = 5e3 + 10, M = (5e4 + N * 2) * 2, INF = 0x3f3f3f3f;
int n, m, S, T;
int h[N], e[M], ne[M], idx;
int f[M];
int q[N], d[N], cur[N];
int p[N], dg[N], w[M];

void add(int a, int b, int c1, int c2) {
	e[idx] = b, f[idx] = c1, ne[idx] = h[a], h[a] = idx++;
	e[idx] = a, f[idx] = c2, ne[idx] = h[b], h[b] = idx++;
}

bool bfs() {
	int hh = 0, tt = 0;
	memset(d, -1, sizeof d);
	q[0] = S, d[S] = 0, cur[S] = h[S];
	while (hh <= tt) {
		int t = q[hh++];
		for (int i = h[t]; i != -1; i = ne[i]) {
			int j = e[i];
			if (d[j] == -1 && f[i]) {
				d[j] = d[t] + 1;
				cur[j] = h[j];
				if (j == T)return 1;
				q[++tt] = j;
			}
		}
	}
	return 0;
}

int find(int u, int limit) {
	if (u == T)return limit;
	int flow = 0;
	for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
		int j = e[i];
		cur[u] = i;
		if (d[j] == d[u] + 1 && f[i]) {
			int t = find(j, min(f[i], limit - flow));
			if (!t)d[j] = -1;
			f[i] -= t, f[i ^ 1] += t, flow += t;
		}
	}
	return flow;
}

int dinic() {
	int ret = 0, flow;
	while (bfs())while (flow = find(S, INF))ret += flow;
	return ret;
}

int main() {
	cin >> n >> m;
	S = 0, T = n + 1;
	memset(h, -1, sizeof h);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &p[i]);
		p[i] *= -1;
	}
	for (int i = 1,a,b,c; i <= m; i++) {
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c, c);
		dg[a] += c, dg[b] += c;
	}
	int U = 0;
	for (int i = 1; i <= n; i++)U = max(U, 2 * p[i] + dg[i]);
	for (int i = 1; i <= n; i++) {
		add(S, i, U, 0);
		add(i, T, U - 2 * p[i] - dg[i], 0);
	}
	int t = dinic();
	printf("%d\n", (U * n - t) / 2);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值