题意:有两个人要逃狱,先后从监狱到达车站,第一个人经过的路径第二个人不能走,每条路径都有走过要花费的时间,求两个人总共要花费的时间,如果无法满足条件输出返回监狱。
题解:这是个最小费用最大流的问题,可以看作是先从起点到终点,再从终点到起点,第一次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;
}