题解:洛谷 P3003 Apple Delivery
【题目来源】
洛谷:P3003 [USACO10DEC] Apple Delivery S
【题目描述】
Bessie 有两只鲜红的苹果要送给她在牛群中的两个朋友。当然,她要走 \(C\) 条牛道(\(1 \le C \le 2\times 10^5\)),这些牛道构成了一个常见的图,方便地连接了 \(P\) 个牧场(\(1 \le P \le 10^5\)),这些牧场的编号从 \(1\) 到 \(P\):没有牛道从一个牧场通向自身,牛道是双向的,每条牛道都有一个相关的距离,最重要的是,总是可以从任何一个牧场到达另一个牧场。每条牛道连接两个不同的牧场 \(P1_i\)(\(1 \le P1_i \le P\))和 \(P2_i\)(\(1 \le P2_i \le P\)),它们之间的距离为 \(D_i\)。所有距离 \(D_i\) 的总和不超过 \(2\times 10^9\)。
Bessie 要从牧场 \(PB\)(\(1 \le PB \le P\))出发,按任意顺序访问牧场 \(PA_1\)(\(1 \le PA_1 \le P\))和 \(PA_2\)(\(1 \le PA_2 \le P\)),送完两个苹果后,求她必须行走的最小总距离。当然,这三个牧场是不同的。
考虑这个用括号标注牧场编号和牛道距离的地图:
3 2 2
[1]-----[2]------[3]-----[4]
\ / \ /
7\ /4 \3 /2
\ / \ /
[5]-----[6]------[7]
1 2
如果 Bessie 从牧场 \([5]\) 出发,将苹果送到牧场 \([1]\) 和 \([4]\),她的最佳路径是:
\(5\) -> \(6\) -> \(7\) -> \(4*\) -> \(3\) -> \(2\) -> \(1*\)
总距离为 \(12\)。
【输入】
* 第 \(1\) 行:包含五个用空格分隔的整数:$C, P, PB, PA_1 $ 和 \(PA_2\)。
* 第 \(2\) 行到第 \(C+1\) 行:第 \(i+1\) 行描述牛道 \(i\),给出它连接的两个牧场及其之间的距离:\(P1_i\), \(P2_i\), \(D_i\)。
【输出】
* 第 \(1\) 行:Bessie 必须行走的最短距离。
【输入样例】
9 7 5 1 4
5 1 7
6 7 2
4 7 2
5 6 1
5 2 4
4 3 2
1 2 3
3 2 2
2 6 3
【输出样例】
12
【核心思想】
-
问题分析:给定一个 \(P\) 个点、\(C\) 条边的无向带权连通图。Bessie 从起点 \(PB\) 出发,需要访问两个目标点 \(PA_1\) 和 \(PA_2\)(顺序不限),求总行走距离的最小值。这是一个单源最短路问题,关键点在于两个目标点的访问顺序只有两种可能:\(PB \to PA_1 \to PA_2\) 或 \(PB \to PA_2 \to PA_1\)。由于图是无向的,两者距离对称,因此答案 = \(\min(\text{dist}(PB, PA_1), \text{dist}(PB, PA_2)) + \text{dist}(PA_1, PA_2)\)。
-
算法选择:
- Dijkstra 算法:由于边权均为正,使用堆优化的 Dijkstra 计算单源最短路。只需计算三次最短路:
- 从 \(PB\) 出发,得到到 \(PA_1\) 和 \(PA_2\) 的距离。
- 从 \(PA_1\) 出发,得到到 \(PA_2\) 的距离。
- 复杂度:\(O((P + C) \log P)\),\(P \le 10^5, C \le 2\times10^5\),完全可行。
- Dijkstra 算法:由于边权均为正,使用堆优化的 Dijkstra 计算单源最短路。只需计算三次最短路:
-
关键步骤:
- 读入:读取 \(C, P, PB, PA_1, PA_2\) 以及 \(C\) 条无向边。
- 第一次 Dijkstra:从 \(PB\) 出发,得到
distPB,取to1 = distPB[PA1],to2 = distPB[PA2]。 - 第二次 Dijkstra:从 \(PA_1\) 出发,得到
distA1,between = distA1[PA2]。 - 计算答案:
ans = min(to1, to2) + between。 - 输出:
ans。
-
时间/空间复杂度:
- 时间复杂度:\(O((P + C) \log P)\)。
- 空间复杂度:\(O(P + C)\)。
-
最短路与路径分解:
- 顺序无关性:由于图是无向的,\(PB \to PA_1 \to PA_2\) 和 \(PB \to PA_2 \to PA_1\) 的总距离分别是 \(\text{dist}(PB, PA_1) + \text{dist}(PA_1, PA_2)\) 和 \(\text{dist}(PB, PA_2) + \text{dist}(PA_1, PA_2)\)。因此只需取 \(\min(\text{dist}(PB, PA_1), \text{dist}(PB, PA_2)) + \text{dist}(PA_1, PA_2)\)。
- Dijkstra 复用:通过计算三次最短路,可以覆盖所有情况。
- 适用场景:这类“按任意顺序访问多个目标点的最短路”问题,通常利用最短路距离的组合性质。
【算法标签】
普及 #Dijkstra
【代码详解】
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII; // 存储 (距离, 节点编号) 或 (节点编号, 边权)
const int N = 100005;
int n, m, pb, pa1, pa2; // n: 节点总数(P), m: 边数(C), pb: 起点, pa1, pa2: 两个目标点
vector<PII> g[N]; // 邻接表存图, 存储 (邻接点, 边权)
int dis[N]; // 单源最短距离数组
// Dijkstra 算法, 计算从源点 s 到所有节点的最短距离
void dij(int s)
{
memset(dis, 0x3f, sizeof(dis)); // 初始化距离为极大值
priority_queue<PII, vector<PII>, greater<PII>> q; // 小根堆, (距离, 节点)
dis[s] = 0;
q.push({0, s});
while (!q.empty())
{
auto [d, u] = q.top(); // 取出当前距离最小的节点
q.pop();
if (d > dis[u]) continue; // 如果该距离已过时, 跳过
for (auto [v, w] : g[u]) // 遍历邻接边
{
if (dis[v] > dis[u] + w) // 可以松弛
{
dis[v] = dis[u] + w;
q.push({dis[v], v});
}
}
}
}
int main()
{
cin >> m >> n >> pb >> pa1 >> pa2; // 输入边数, 点数, 起点, 两个目标点
while (m--) // 读入所有边 (双向)
{
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
// 情况1: 从 pb 出发, 先到较近的目标, 再到另一个目标
dij(pb); // 计算从 pb 到所有点的最短距离
int res1 = min(dis[pa1], dis[pa2]); // pb 到较近目标的最短距离
// 情况2: 从较近目标到另一个目标 (等价于从 pa1 到 pa2 的最短距离)
dij(pa1); // 计算从 pa1 到所有点的最短距离
int res2 = dis[pa2]; // pa1 到 pa2 的最短距离
// 总距离 = min(pb->pa1, pb->pa2) + pa1->pa2
// 因为两个目标点顺序可以交换, 上式已覆盖两种情况
cout << res1 + res2 << endl;
return 0;
}
【运行结果】
9 7 5 1 4
5 1 7
6 7 2
4 7 2
5 6 1
5 2 4
4 3 2
1 2 3
3 2 2
2 6 3
12
浙公网安备 33010602011771号