建立正向图与反向图,分别以聚会农场为源点运行两次堆优化 Dijkstra 算法,求出所有牛往返最短路之和的最大值。
OJ: roj
题目 ID: 1497
难度:普及+/提高-
标签:最短路Dijkstra反向图
创建: 2026-09-30 14:38
更新: 2026-10-07 11:01
形式化题目
给定一个包含
定义两点
正解
思路
计算每个顶点的往返最短路可分为两个独立的部分:
- 回程部分:从目标顶点
出发到达每个顶点 的最短路 ; - 去程部分:从每个顶点
出发到达目标顶点 的最短路 。
对于回程部分,这本质上是以
对于去程部分,如果对每个顶点
这里可以借助**反向图(Reverse Graph)**的性质进行优化:
- 构造原图
的反向图 ,其中反向边集合定义为 。 - 原图中任意一条由
到 且权重和为 的有向路径: 在反向图中完全对应一条由 到 且权重和相同的路径: - 因此,原图中
的最短路长度,严格等于反向图中 的最短路长度:
于是,求所有顶点到
算法全流程:
- 读取输入时同时建立原图的邻接表
adj与反向图的邻接表rev_adj; - 在原图上以
为起点跑一次 Dijkstra,记距离数组为 dist_from_x; - 在反向图上以
为起点跑一次 Dijkstra,记距离数组为 dist_to_x; - 遍历
,取 dist_to_x[i] + dist_from_x[i]的最大值输出。
flowchart LR
subgraph G [原图 G: 求 X -> i]
X1((X)) -->|Dijkstra| I1((各个 i))
end
subgraph GR [反向图 GR: 求 i -> X 等价于 X -> i]
X2((X)) -->|Dijkstra| I2((各个 i))
end
G --> Sum["求和 dist(i, X) + dist(X, i)"]
GR --> Sum
Sum --> Ans["取全局最大值 max"]
代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 14:40
# update_at: 2026-09-30 14:40
import heapq
import sys
INF = 10**18
def dijkstra(n: int, start: int, adj: list[list[tuple[int, int]]]) -> list[int]:
"""计算从 start 出发到图中所有点的单源最短路距离。"""
dist = [INF] * (n + 1)
dist[start] = 0
pq = [(0, start)]
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]:
continue
for v, w in adj[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(pq, (dist[v], v))
return dist
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n, m, x = next(data), next(data), next(data)
adj: list[list[tuple[int, int]]] = [[] for _ in range(n + 1)]
rev_adj: list[list[tuple[int, int]]] = [[] for _ in range(n + 1)]
for _ in range(m):
u, v, w = next(data), next(data), next(data)
adj[u].append((v, w))
rev_adj[v].append((u, w))
dist_from_x = dijkstra(n, x, adj)
dist_to_x = dijkstra(n, x, rev_adj)
ans = max(dist_to_x[i] + dist_from_x[i] for i in range(1, n + 1))
print(ans)
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-05 04:33
* update_at: 2026-10-05 04:33
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1005; // 农场数上限(N <= 1000)
const int MAXM = 100005; // 每个图的边数上限(M <= 100000)
const ll INF = 1e18;
int n, m, x; // n 头牛、m 条有向道路、派对农场编号 x
// 正向图:链式前向星,用来求 x 到各点的最短路(回程)
int head[MAXN], nxt[MAXM], to[MAXM];
ll w[MAXM];
int edge_cnt;
// 反向图:把每条边 u->v 倒过来存成 v->u,用来求各点到 x 的最短路(去程)
// 因为原图中 i 到 x 的最短路,等于反向图中 x 到 i 的最短路。
int rev_head[MAXN], rev_nxt[MAXM], rev_to[MAXM];
ll rev_w[MAXM];
int rev_edge_cnt;
ll dist_from_x[MAXN]; // dist_from_x[i]:原图中 x -> i 的最短路长度
ll dist_to_x[MAXN]; // dist_to_x[i]:反向图中 x -> i 的最短路长度,即原图 i -> x
// 向正向图加一条 u -> v、长度 c 的边。
void add_edge(int u, int v, ll c) {
edge_cnt++;
to[edge_cnt] = v;
w[edge_cnt] = c;
nxt[edge_cnt] = head[u];
head[u] = edge_cnt;
}
// 向反向图加一条 u -> v、长度 c 的边(存的是原图 v -> u 的反向边)。
void add_rev_edge(int u, int v, ll c) {
rev_edge_cnt++;
rev_to[rev_edge_cnt] = v;
rev_w[rev_edge_cnt] = c;
rev_nxt[rev_edge_cnt] = rev_head[u];
rev_head[u] = rev_edge_cnt;
}
// 堆优化 Dijkstra:在给定的图上求源点 start 到所有点的最短路,结果写入 dist。
// 传入一组链式前向星数组,让正向图与反向图共用同一份松弛逻辑。
void dijkstra(int start, int *g_head, int *g_nxt, int *g_to, ll *g_w, ll *dist) {
for (int i = 1; i <= n; i++) dist[i] = INF;
dist[start] = 0;
// 小根堆,元素为 (当前最短路长度, 顶点编号)
priority_queue<pair<ll, int>, vector<pair<ll, int> >, greater<pair<ll, int> > > pq;
pq.push(make_pair(0, start));
while (!pq.empty()) {
pair<ll, int> cur = pq.top();
pq.pop();
ll d = cur.first;
int u = cur.second;
if (d > dist[u]) continue; // 堆里过期的旧记录,直接跳过
for (int i = g_head[u]; i; i = g_nxt[i]) {
int v = g_to[i];
if (dist[u] + g_w[i] < dist[v]) {
dist[v] = dist[u] + g_w[i];
pq.push(make_pair(dist[v], v));
}
}
}
}
void read_input() {
cin >> n >> m >> x;
for (int i = 1; i <= m; i++) {
int a, b;
ll t;
cin >> a >> b >> t;
add_edge(a, b, t); // 原图 a -> b
add_rev_edge(b, a, t); // 反向图 b -> a,等价于把原边倒过来
}
}
void solve() {
// 回程:在原图上以 x 为源点做一次 Dijkstra
dijkstra(x, head, nxt, to, w, dist_from_x);
// 去程:在反向图上以 x 为源点做一次 Dijkstra
dijkstra(x, rev_head, rev_nxt, rev_to, rev_w, dist_to_x);
// 每头牛的花费是去程加回程,答案取其中最大的一条
ll ans = 0;
for (int i = 1; i <= n; i++) {
ll round_trip = dist_to_x[i] + dist_from_x[i];
if (round_trip > ans) ans = round_trip;
}
cout << ans << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
read_input();
solve();
return 0;
}复杂度
- 时间复杂度:建图遍历
条边需 时间;两次堆优化 Dijkstra 的时间复杂度均为 ,最后遍历 个顶点求最大值耗时 。整体时间复杂度为 ,对于 可以在数十毫秒内快速完成。 - 空间复杂度:存储正向邻接表和反向邻接表各需要
空间,Dijkstra 堆与距离数组占用 空间。整体空间复杂度为 ,远低于 128MB 的内存限制。
总结
多源单汇的最短路问题是图论中的经典模型。由于在有向图中“所有点到一个固定终点”等价于“在边全反向的图中从该点出发到所有点”,利用反向图可以将原本需要