往返距离等于 i 到 x 再加 x 到 i;原图从 x 跑一次 Dijkstra,反图再从 x 跑一次 Dijkstra,就能得到所有点的来回最短路。
OJ: luogu
题目 ID: P1821
难度:普及/提高-
标签:最短路图论堆
日期: 2026-06-20 03:25
题意
有
图是有向图,每条边有长度。
每头牛都要:
- 从自己家走到
- 参加完派对后再从
走回自己家
两段都走最短路。
要求所有牛的“往返最短路长度”里的最大值。
反图直觉图
这张图展示了为什么“求
digraph G {
rankdir=LR;
subgraph cluster0 {
label="原图";
color=gray;
a [label="i"];
b [label="..."];
c [label="x"];
a -> b -> c;
}
subgraph cluster1 {
label="反图";
color=gray;
d [label="x"];
e [label="..."];
f [label="i"];
d -> e -> f;
}
}
原图里从
思路
先看一个最直接的小数据暴力:
cpp
// brute.cpp:用 Floyd 求任意两点最短路。
// 小数据下可以直接求出 i -> x 和 x -> i,再枚举最大往返距离。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const long long INF = (1LL << 60);
int n, m, x;
long long dist_arr[MAXN][MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> x;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j) {
dist_arr[i][j] = 0;
}
else {
dist_arr[i][j] = INF;
}
}
}
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
if (w < dist_arr[u][v]) {
dist_arr[u][v] = w;
}
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (dist_arr[i][k] + dist_arr[k][j] < dist_arr[i][j]) {
dist_arr[i][j] = dist_arr[i][k] + dist_arr[k][j];
}
}
}
}
long long answer = 0;
for (int i = 1; i <= n; i++) {
answer = max(answer, dist_arr[i][x] + dist_arr[x][i]);
}
cout << answer << '\n';
return 0;
}暴力做法可以用 Floyd:
- 先求任意两点最短路
- 对每个点
计算 - 取最大值
但这题真正的关键不是 Floyd,而是把“去程”和“回程”拆开。
对于每头牛
- 去派对:
- 回家:
回家这一段很简单,直接在原图上从
难点是去派对这一段:我们想要的是所有
这里有一个常用技巧:
- 把所有边反向,建一张反图
这样原图里:
就会变成反图里:
于是“所有点到
所以整题只要跑两次 Dijkstra:
- 原图从
出发,得到 - 反图从
出发,得到
最后枚举每个点
取最大值即可。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000 + 5;
const int MAXM = 100000 + 5;
const long long INF = (1LL << 60);
struct HeapNode {
int u;
long long dist;
bool operator < (const HeapNode &other) const {
return dist > other.dist;
}
};
int n, m, x;
// 原图:求 x -> i 的最短路
int head1[MAXN], to1[MAXM], nxt1[MAXM], w1[MAXM], cnt1;
// 反图:求 i -> x 的最短路,等价于在反图里求 x -> i
int head2[MAXN], to2[MAXM], nxt2[MAXM], w2[MAXM], cnt2;
long long dist_go[MAXN];
long long dist_back[MAXN];
bool vis[MAXN];
void init_graph() {
cnt1 = 0;
cnt2 = 0;
for (int i = 1; i <= n; i++) {
head1[i] = 0;
head2[i] = 0;
}
}
void add_edge(int head[], int to[], int nxt[], int w[], int &cnt, int u, int v, int len) {
cnt++;
to[cnt] = v;
w[cnt] = len;
nxt[cnt] = head[u];
head[u] = cnt;
}
// 在给定的图上,从 start 跑一次 Dijkstra。
void dijkstra(int start, int head[], int to[], int nxt[], int w[], long long dist[]) {
for (int i = 1; i <= n; i++) {
dist[i] = INF;
vis[i] = false;
}
priority_queue<HeapNode> pq;
dist[start] = 0;
pq.push({start, 0});
while (!pq.empty()) {
HeapNode cur = pq.top();
pq.pop();
int u = cur.u;
if (vis[u]) {
continue;
}
vis[u] = true;
for (int i = head[u]; i != 0; i = nxt[i]) {
int v = to[i];
long long nd = dist[u] + w[i];
if (nd < dist[v]) {
dist[v] = nd;
pq.push({v, nd});
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> x;
init_graph();
for (int i = 1; i <= m; i++) {
int u, v, len;
cin >> u >> v >> len;
add_edge(head1, to1, nxt1, w1, cnt1, u, v, len);
add_edge(head2, to2, nxt2, w2, cnt2, v, u, len);
}
dijkstra(x, head1, to1, nxt1, w1, dist_go);
dijkstra(x, head2, to2, nxt2, w2, dist_back);
long long answer = 0;
for (int i = 1; i <= n; i++) {
answer = max(answer, dist_go[i] + dist_back[i]);
}
cout << answer << '\n';
return 0;
}复杂度
两次堆优化 Dijkstra:
最后扫一遍所有点:
总复杂度:
空间复杂度:
总结
这题最值得记住的是反图这个转换:
- 求“所有点到某个固定点”的最短路
- 可以改成“反图里从这个固定点出发”的单源最短路
所以本题本质上是:
- 原图一次 Dijkstra
- 反图一次 Dijkstra
- 合并去程和回程答案
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

