题解:洛谷 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

【核心思想】

  1. 问题分析:给定一个 \(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)\)

  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\),完全可行。
  3. 关键步骤

    • 读入:读取 \(C, P, PB, PA_1, PA_2\) 以及 \(C\) 条无向边。
    • 第一次 Dijkstra:从 \(PB\) 出发,得到 distPB,取 to1 = distPB[PA1]to2 = distPB[PA2]
    • 第二次 Dijkstra:从 \(PA_1\) 出发,得到 distA1between = distA1[PA2]
    • 计算答案ans = min(to1, to2) + between
    • 输出ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O((P + C) \log P)\)
    • 空间复杂度:\(O(P + C)\)
  5. 最短路与路径分解

    • 顺序无关性:由于图是无向的,\(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
posted @ 2026-08-31 09:27  团爸讲算法  阅读(8)  评论(0)    收藏  举报