uva 10806(最大流最小费)

题意:有两个人要逃狱,先后从监狱到达车站,第一个人经过的路径第二个人不能走,每条路径都有走过要花费的时间,求两个人总共要花费的时间,如果无法满足条件输出返回监狱。

题解:这是个最小费用最大流的问题,可以看作是先从起点到终点,再从终点到起点,第一次spfa后,要把走过的路径在从终点到起点方向的改成极大值(不能再走),而起点到终点方向的改成原来的负值(这里刚开始死活不明白),利用了最大流中反向弧的作用,给了之前选择的路径一个反悔“改正”的机会,举个栗子:

6(起点1 终点 6)

8(边数)

1 2 1 (边的两个结点序号和边的花费)

2 3 1

3 6 1

1 4 100

4 5 100

5 6 100

1 3 10

2 6 10

画个图很明显第一次一定会选 1 -> 2 -> 3 -> 6 这条路,因为总花费是3

但第二次如果只是单纯不走之前走的路径 第二次会选 1 -> 4 -> 5 -> 6 只有这条路可走,总花费是 300 + 3 = 303

真正的最好的选择是 1 -> 2 -> 6 和 1 -> 3 -> 6 总花费是 11 + 11 = 22

那么怎么改正选了花费是3的路呢,就让最大流中反向弧的发挥用处,按这题的意思,走过的路不能再走,所以之前走过的路把终点到起点方向的路花费变成INF,但相反方向改为之前的负值,第二次spfa时,先选了6 -> 2花费10,然后发现 2 -> 3 是10 + (-1) = 9 于是又从结点2走到了3,然后从3 -> 1 花费10 总共花费19 与之前的1 -> 2 -> 3 -> 6加起来刚好也是22,可是却发现2 -> 3 走了两边,第一次花费1 第二次花费-1,可不可以就认为其实是没走的,那么一下子就明晰起来了,其实第二次spfa改正了第一次犯的错误,并以另一种减花费的方式改正回来,两条路其实就是1 -> 2 -> 6 和 6 -> 3 -> 1。

#include <stdio.h>
#include <string.h>
const int N = 105;
const int M = 10000;
const int INF = 0x3f3f3f3f;
int n, m, d[N], vis[N], pa[N], q[M], g[N][N];

int spfa(int sta, int end) {
	memset(d, INF, sizeof(d));
	memset(vis, 0, sizeof(vis));
	d[sta] = 0;
	vis[sta] = 1;
	int front = 0, rear = 0;
	q[rear++] = sta;
	while (front != rear) {
		int top = q[front];
		front++;
		vis[top] = 0;
		for (int i = 1; i <= n; i++) {
			if (d[i] > d[top] + g[top][i]) {
				d[i] = d[top] + g[top][i];
				pa[i] = top;
				if (!vis[i]) {
					vis[i] = 1;
					q[rear++] = i;
				}
			}
		}
	}
	return d[end];
}

void solve(int k) {
	if (pa[k] == -1)
		return;
	g[pa[k]][k] = -g[k][pa[k]];
	g[k][pa[k]] = INF;
	solve(pa[k]);
}

int main() {
	int a, b, c;
	while (scanf("%d", &n) && n) {
		scanf("%d", &m);
		memset(vis, 0, sizeof(vis));
		memset(pa, -1, sizeof(pa));
		memset(g, INF, sizeof(g));
		for (int i = 0; i < m; i++) {
			scanf("%d%d%d", &a, &b, &c);
			g[a][b] = g[b][a] = c;
		}
		int res = spfa(1, n);
		solve(n);
		res += spfa(n, 1);
		if (res < INF)
			printf("%d\n", res);
		else
			printf("Back to jail\n");
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值