UVa 10806 Dijkstra Dijkstra
本题要求在无向加权图中寻找从节点1到节点n的两条边不相交路径,使得总长度最小,否则输出"Back to jail"。解题核心是将问题转化为最小费用最大流模型:将原图每条无向边拆为两条容量为1、费用为边长的有向边,并添加超级源点0,连接节点1的容量设为2。使用SPFA算法实现的最小费用最大流算法求解,若最大流量达到2则输出总费用,否则无解。该建模确保了边不重复使用,并求得总耗时最短的两条独立路径。
题目描述
你是一名政治犯,被关在监狱中。你的狱友设计了一个逃跑计划:他先越狱,沿着城市街道跑到火车站,然后打电话给你,你再从监狱出发,沿着与他完全不同的路径(不能走他走过的任何一条街道,但可以经过相同的路口)跑到火车站会合,然后一起上火车离开。
给定一个无向加权图,节点 111 是监狱,节点 nnn 是火车站。你需要找到两条从节点 111 到节点 nnn 的边不相交的路径,使得这两条路径的总长度最小。如果无法找到两条这样的路径,则输出 Back to jail。
输入格式
输入包含多个测试用例。每个测试用例以整数 nnn (2≤n≤1002 \leq n \leq 1002≤n≤100)开始,表示节点数。接下来一行是整数 mmm ,表示街道数。随后 mmm 行每行包含三个整数 u,v,tu, v, tu,v,t ,表示连接节点 uuu 和 vvv 的街道长度为 ttt (1≤t≤10001 \leq t \leq 10001≤t≤1000 )。输入以一行包含 000 结束。
输出格式
对于每个测试用例,输出一个整数表示两人从离开监狱到都登上火车所需的最短总时间(秒)。如果无法完成,输出 Back to jail。
题目分析与建模
本题的核心是:在无向图中寻找两条从源点 111 到汇点 nnn 的边不相交路径,使得它们的总长度最小。
这实际上是一个最小费用最大流问题:
- 节点:图中的每个路口。
- 边:将原图的每条无向边转化为两条方向相反的有向边,每条有向边的容量设为 111 ,费用设为该边的长度。
- 源点和汇点:添加一个超级源点 000 ,从 000 向节点 111 连接一条容量为 222 、费用为 000 的边,表示需要两条从 111 到 nnn 的流。汇点就是节点 nnn 。
这样,问题转化为:在构建的流网络中寻找从 000 到 nnn 的流量为 222 的最小费用流。如果最大流量小于 222 ,则无解。
为什么可以这样建模?
- 每条边的容量为 111 ,确保每条边最多被使用一次,满足“边不相交”的条件。
- 由于原图是无向图,我们将其拆分为两条有向边,分别表示两个方向的通行。
- 从超级源点 000 到节点 111 的容量 222 表示需要两条路径。
- 最小费用流算法会寻找总长度最小的两条路径。
算法设计
使用 SPFA(Shortest Path Faster Algorithm)\texttt{SPFA(Shortest Path Faster Algorithm)}SPFA(Shortest Path Faster Algorithm) 实现的 最小费用最大流(MCMF\texttt{MCMF}MCMF) 算法:
- 构建网络:根据输入构建带容量的有向图,每条边存储正向边和反向边。
- 寻找增广路:每次使用 SPFA\texttt{SPFA}SPFA 寻找从源点到汇点的最短路径(按费用计算),同时记录路径。
- 更新流量:沿找到的增广路增加流量,更新边的残余容量,并累加费用。
- 重复:直到无法找到增广路或流量达到 222 为止。
- 判断结果:若最终流量为 222 ,输出总费用;否则输出
Back to jail。
代码实现
// Dijkstra Dijkstra
// UVa ID: 10806
// Verdict: Accepted
// Submission Date: 2017-10-05
// UVa Run Time: 0.000s
//
// 版权所有(C)2017,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAXV = 110, MAXE = 20100, INF = 0x3f3f3f3f;
// 弧结构体:表示网络流中的一条边
struct arc {
int u, v, capacity, residual, cost, next;
} arcs[MAXE];
int idx, source, sink, dist[MAXV], head[MAXV], parent[MAXV], visited[MAXV];
int n, m, fee, flow; // fee:总费用,flow:总流量
// SPFA 算法寻找最短增广路(按费用)
bool spfa() {
for (int i = 0; i < MAXV; i++)
dist[i] = INF, parent[i] = -1, visited[i] = 0;
dist[source] = 0, visited[source] = 1;
queue<int> q; q.push(source);
while (!q.empty()) {
int u = q.front(); q.pop(); visited[u] = 0;
for (int i = head[u]; ~i; i = arcs[i].next) {
arc e = arcs[i];
// 如果残余容量大于0且可以松弛
if (e.residual > 0 && dist[e.v] > dist[u] + e.cost) {
dist[e.v] = dist[u] + e.cost;
parent[e.v] = i; // 记录路径
if (!visited[e.v]) {
q.push(e.v);
visited[e.v] = 1;
}
}
}
}
return dist[sink] < INF; // 如果汇点可达,说明找到增广路
}
// 添加一条从 u 到 v 的弧,容量为 capacity,费用为 cost
void addArc(int u, int v, int capacity, int cost) {
// 正向弧
arcs[idx] = (arc){u, v, capacity, capacity, cost, head[u]};
head[u] = idx++;
// 反向弧
arcs[idx] = (arc){v, u, capacity, 0, -cost, head[v]};
head[v] = idx++;
}
// 最小费用最大流主函数
void mcmf() {
fee = flow = 0;
// 每次寻找增广路
while (spfa()) {
int delta = INF;
// 计算增广路上的最小残余容量
for (int i = parent[sink]; ~i; i = parent[arcs[i].u])
delta = min(delta, arcs[i].residual);
flow += delta; // 增加流量
fee += delta * dist[sink]; // 增加费用
// 更新残余网络
for (int i = parent[sink]; ~i; i = parent[arcs[i].u]) {
arcs[i].residual -= delta;
arcs[i ^ 1].residual += delta;
}
}
}
int main(int argc, char *argv[]) {
cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
while (cin >> n, n > 0) {
cin >> m;
idx = 0, source = 0, sink = n;
memset(head, -1, sizeof(head));
// 读入无向边,转化为两条有向弧
for (int i = 1, u, v, t; i <= m; i++) {
cin >> u >> v >> t;
addArc(u, v, 1, t);
addArc(v, u, 1, t);
}
// 添加超级源点到节点1的弧,容量为2,表示需要两条路径
addArc(0, 1, 2, 0);
mcmf();
if (flow < 2) cout << "Back to jail\n";
else cout << fee << '\n';
}
return 0;
}
复杂度分析
- 时间复杂度:每次 SPFA\texttt{SPFA}SPFA 寻找增广路的时间复杂度为 O(VE)O(VE)O(VE) ,由于最多增广 222 次(流量需求为 222 ),总时间复杂度约为 O(VE)O(VE)O(VE) 。在本题限制下(n≤100,m≤n(n−1)/2n \leq 100, m \leq n(n-1)/2n≤100,m≤n(n−1)/2),完全可行。
- 空间复杂度:主要存储图的结构,为 O(V+E)O(V + E)O(V+E) 。
总结
本题通过将边不相交最短双路径问题转化为最小费用最大流问题,巧妙地利用了网络流模型中的容量限制来保证边的不重复使用。实现上使用了经典的 SPFA\texttt{SPFA}SPFA 算法求解最小费用流,代码简洁高效。需要注意的是,无向图需拆分为两条有向边,并正确设置反向弧的容量和费用。
更多推荐



所有评论(0)