只需要比较两种送货顺序:PB->PA1->PA2 和 PB->PA2->PA1。图是无向图,因此求出 PB 到两点的距离和 PA1 到 PA2 的距离后即可直接取最小值。
OJ: luogu
题目 ID: P3003
难度:普及/提高-
标签:最短路图论堆
日期: 2026-06-20 03:53
题意
贝茜从起点 PB 出发,要给 PA1 和 PA2 两个牧场送苹果。
她必须把两个点都访问到,但:
- 先去
PA1再去PA2 - 或先去
PA2再去PA1
顺序可以自己选。
图是无向带权图,要求最小总路程。
思路
先看一个最直接的小数据暴力:
cpp
// brute.cpp:用 Floyd 求任意两点最短路,再直接枚举两种送货顺序。
// 只适合小数据,但最贴近题意。
#include <bits/stdc++.h>
using namespace std;
const int MAXP = 105;
const long long INF = (1LL << 60);
int c, p, pb, pa1, pa2;
long long dist_arr[MAXP][MAXP];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> c >> p >> pb >> pa1 >> pa2;
for (int i = 1; i <= p; i++) {
for (int j = 1; j <= p; j++) {
if (i == j) {
dist_arr[i][j] = 0;
}
else {
dist_arr[i][j] = INF;
}
}
}
for (int i = 1; i <= c; i++) {
int u, v, len;
cin >> u >> v >> len;
if (len < dist_arr[u][v]) {
dist_arr[u][v] = len;
dist_arr[v][u] = len;
}
}
for (int k = 1; k <= p; k++) {
for (int i = 1; i <= p; i++) {
for (int j = 1; j <= p; 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 ans1 = dist_arr[pb][pa1] + dist_arr[pa1][pa2];
long long ans2 = dist_arr[pb][pa2] + dist_arr[pa2][pa1];
cout << min(ans1, ans2) << '\n';
return 0;
}暴力做法是 Floyd:
- 先求任意两点最短路
- 比较两种顺序:
这个思路已经把本题本质暴露出来了:
真正难点根本不在状态设计,而在先想明白“只有两种顺序”。
因为只有两个送货点,所以总路线只有:
因此只要知道这三个关键距离:
答案就能直接写出来。
又因为图是无向图,所以:
于是只需要:
- 从
PB做一次 Dijkstra,拿到PB到两个送货点的距离 - 再从
PA1做一次 Dijkstra,拿到PA1到PA2的距离
最后比较:
取更小的即可。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXP = 100000 + 5;
const int MAXC = 200000 * 2 + 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 c, p, pb, pa1, pa2;
int head[MAXP], to[MAXC], nxt[MAXC], w[MAXC], edge_cnt;
long long dist_arr[MAXP];
bool vis[MAXP];
void init_graph() {
edge_cnt = 0;
for (int i = 1; i <= p; i++) {
head[i] = 0;
}
}
void add_edge(int u, int v, int len) {
edge_cnt++;
to[edge_cnt] = v;
w[edge_cnt] = len;
nxt[edge_cnt] = head[u];
head[u] = edge_cnt;
}
void dijkstra(int start) {
for (int i = 1; i <= p; i++) {
dist_arr[i] = INF;
vis[i] = false;
}
priority_queue<HeapNode> pq;
dist_arr[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_arr[u] + w[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 >> c >> p >> pb >> pa1 >> pa2;
init_graph();
for (int i = 1; i <= c; i++) {
int u, v, len;
cin >> u >> v >> len;
add_edge(u, v, len);
add_edge(v, u, len);
}
dijkstra(pb);
long long d_pb_a1 = dist_arr[pa1];
long long d_pb_a2 = dist_arr[pa2];
dijkstra(pa1);
long long d_a1_a2 = dist_arr[pa2];
// 只有两种顺序:
// 1. PB -> PA1 -> PA2
// 2. PB -> PA2 -> PA1
long long ans1 = d_pb_a1 + d_a1_a2;
long long ans2 = d_pb_a2 + d_a1_a2;
cout << min(ans1, ans2) << '\n';
return 0;
}复杂度
做两次堆优化 Dijkstra:
总复杂度:
空间复杂度:
总结
这题最关键的不是最短路模板,而是先把路线顺序枚举清楚。
一旦发现只有两种顺序,问题就变成:
- 求几个关键点对之间的最短路
所以它本质上是一道“枚举顺序 + 单源最短路”的组合题。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
