2191. 数字梯形问题(网络流,费用流,最大权不相交路径)

活动 - AcWing

给定一个由 n 行数字组成的数字梯形如下图所示。

梯形的第一行有 m 个数字。

从梯形的顶部的 m 个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。

规则 1:从梯形的顶至底的 m 条路径互不相交。

规则 2:从梯形的顶至底的 m 条路径仅在数字结点处相交。

规则 3:从梯形的顶至底的 m 条路径允许在数字结点相交或边相交。

QQ截图20200727103803.png

对于给定的数字梯形,分别按照规则 1,规则 2,和规则 3 计算出从梯形的顶至底的 m 条路径,使这 m 条路径经过的数字总和最大。

输入格式

第 1 行中有 2 个正整数 m 和 n,分别表示数字梯形的第一行有 m 个数字,共有 n 行。

接下来的 n 行是数字梯形中各行的数字。第 1 行有 m 个数字,第 2 行有 m+1 个数字,以此类推。

输出格式

将按照规则 1,规则 2,和规则 3 计算出的最大数字总和输出,每行输出一个最大总和。

数据范围

1≤n,m≤20,
梯形中的数字范围 [1,1000]。

输入样例:
2 5
2 3
3 4 5
9 10 9 1
1 1 10 1 1
1 1 10 12 1 1
输出样例:
66
75
77

解析: 

边只能走一次,只需要将边的容量设置成 1 就可以。点只能走一次就需要用一个常用的拆点技巧,将每个点拆成入点和出点,对每个点的限制就可以对入点到出点之间的边做限制,这里每个点只能走一次,那么就将每个点的入点到出点之间的边的容量设置成 1。

然后起点是所有的顶点,终点是所有的底点,因此从源点向所有顶点连一条边,容量是 1。从所有底点向汇点连一条边,如果是规则一,那么容量就是 1,如果是规则二、三,由于点可以相交,最终可能某一个底点是多条路径的终点,因此这时容量应该设成 +∞。

到此为止这个流网络就构建好了,简单证明一下就可以发现,原问题的任意一个可行方案都和流网络中任意一个可行流是一一对应的,并且由于所有方案都有 m 条路径,而源点出去的流量只有 m,因此任意一个可行方案都对应了一个最大流。

然后本题要求的是所有方案中经过点的权值和最大的路径,即权值和最大的一个最大流,我们将每个点的入点到出点的边的费用设置成对应的权值,其余边的费用都设置成 0,那么权值和最大的最大流就等价于最大费用最大流,每次根据不同的规则放开点和边的限制重新求一遍最大费用最大流即可。

#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 = 1200 + 100, M = (1200 * 2) * 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];
int id[610][610], cost[610][610];

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] = INF;
	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 ret = 0;
	while (spfa()) {
		int t = incf[T];
		ret += t * d[T];
		for (int i = T; i != S; i = e[pre[i] ^ 1]) {
			f[pre[i]] -= t;
			f[pre[i] ^ 1] += t;
		}
	}
	return ret;
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值