路线被强制经过 1 号牧场,所以先从 1 号点跑一次 Dijkstra,任意询问答案都是 dist[p] + dist[q]。
OJ: luogu
题目 ID: P2984
难度:普及/提高-
标签:最短路图论堆
日期: 2026-06-20 03:17
题意
给一张带正边权的无向图,1 号牧场是仓库。
每个询问给出一头公牛所在的牧场
这头公牛必须先到 1 号牧场拿巧克力,再从 1 号牧场走到
要求输出每个询问的最短总路程。
思路
先看一个最直接的小数据暴力:
cpp
// brute.cpp:Floyd 求任意两点最短路,再回答每个询问。
// 只适合小数据,但逻辑最直接。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 205;
const long long INF = (1LL << 60);
int n, m, b;
long long dist_arr[MAXN][MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> b;
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;
dist_arr[v][u] = 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];
}
}
}
}
while (b--) {
int p, q;
cin >> p >> q;
cout << dist_arr[p][1] + dist_arr[1][q] << '\n';
}
return 0;
}暴力做法是 Floyd:
- 先求任意两点最短路
- 每个询问直接输出
这个做法很好理解,但这题真正的关键观察非常短:
- 路线被强制拆成
和
也就是说,询问之间的公共部分就是:
- 所有答案都要用到“从 1 号点到其他所有点的最短路”
于是只要:
- 从
1号点跑一次 Dijkstra - 记下
表示 的最短距离 - 对每个询问输出
因为图是无向图,所以:
dist(1, q)也已经在同一次最短路里求出来了
所以整题只需要一次单源最短路。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 50000 + 5;
const int MAXM = 100000 * 2 + 5;
const long long INF = (1LL << 60);
struct Node {
int u;
long long dist;
bool operator < (const Node &other) const {
return dist > other.dist;
}
};
int n, m, b;
int head[MAXN], to[MAXM], nxt[MAXM], weight_arr[MAXM], edge_cnt;
long long dist_arr[MAXN];
bool vis[MAXN];
void init_graph() {
edge_cnt = 0;
for (int i = 1; i <= n; i++) {
head[i] = 0;
}
}
void add_edge(int u, int v, int w) {
edge_cnt++;
to[edge_cnt] = v;
weight_arr[edge_cnt] = w;
nxt[edge_cnt] = head[u];
head[u] = edge_cnt;
}
// 从 1 号牧场出发做单源最短路。
void dijkstra(int start) {
for (int i = 1; i <= n; i++) {
dist_arr[i] = INF;
vis[i] = false;
}
priority_queue<Node> pq;
dist_arr[start] = 0;
pq.push({start, 0});
while (!pq.empty()) {
Node 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_arr[u] + weight_arr[i];
if (nd < dist_arr[v]) {
dist_arr[v] = nd;
pq.push({v, nd});
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> b;
init_graph();
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
add_edge(u, v, w);
add_edge(v, u, w);
}
dijkstra(1);
while (b--) {
int p, q;
cin >> p >> q;
// 路线被强制拆成:p -> 1 -> q
// 所以答案就是 dist[p] + dist[q]。
cout << dist_arr[p] + dist_arr[q] << '\n';
}
return 0;
}复杂度
一次 Dijkstra:
回答
总复杂度:
空间复杂度:
总结
这题是一个非常标准的“先看清路径被什么条件强制拆开”的题。
一旦发现每条路线都必须经过 1,问题就不再是“很多次最短路查询”,而是:
- 先从
1跑一次单源最短路 - 再把每个询问拆成两段距离直接相加
所以本质上仍然是单源最短路模板题。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
