题解:AtCoder AT_awc0021_d Checkpoint Rally

【题目来源】

AtCoder:D - Checkpoint Rally

【题目描述】

Takahashi is participating in an orienteering competition. In this competition, he must visit designated checkpoints in order and reach the goal.
高桥正在参加一场定向越野比赛。在这项比赛中,他必须按顺序访问指定的检查点,并到达终点。

The competition venue has \(N\) locations, numbered from \(1\) to \(N\). There are \(M\) bidirectional roads between these locations. The \(i\)-th road \((1 \leq i \leq M)\) connects location \(U_i\) and location \(V_i\), and takes \(T_i\) minutes to traverse in either direction. There may be multiple roads connecting the same pair of locations. Also, it is not guaranteed that all locations are reachable from each other via roads.
比赛场地有 \(N\) 个地点,编号从 \(1\)\(N\)。这些地点之间有 \(M\) 条双向道路。第 \(i\) 条道路(\(1 \leq i \leq M\))连接地点 \(U_i\) 和地点 \(V_i\),无论沿哪个方向通行都需要 \(T_i\) 分钟。同一对地点之间可能有多条道路连接。此外,不能保证所有地点都可以通过道路互相到达。

Takahashi is currently at location \(1\), the starting point. According to the competition rules, he must visit \(K\) designated checkpoints \(P_1, P_2, \ldots, P_K\) in this exact order, and finally arrive at location \(N\), the goal. Specifically, he departs from location \(1\) and reaches location \(P_1\), then reaches location \(P_2\), \(\ldots\), reaches location \(P_K\), and finally arrives at location \(N\).
高桥目前位于起点地点 \(1\)。根据比赛规则,他必须按照这个确切顺序访问 \(K\) 个指定检查点 \(P_1, P_2, \ldots, P_K\),最后到达终点地点 \(N\)。具体来说,他从地点 \(1\) 出发,到达地点 \(P_1\),然后到达地点 \(P_2\),……,到达地点 \(P_K\),最后到达地点 \(N\)

The rules regarding checkpoint visits are as follows:
关于检查点访问的规则如下:

  • Checkpoints are completed only in the order \(P_1, P_2, \ldots, P_K\). When arriving at a location, if that location is the next checkpoint to be visited, the visit to that checkpoint is completed immediately. No time other than travel time is incurred.
    检查点只能按照 \(P_1, P_2, \ldots, P_K\) 的顺序完成。当到达一个地点时,如果该地点是下一个要访问的检查点,则立即完成对该检查点的访问。除了旅行时间外,不产生其他时间。
  • Since Takahashi starts at location \(1\), if \(P_1 = 1\), the visit to checkpoint \(P_1\) is completed at the moment of departure. In general, when a checkpoint visit is completed, if the next checkpoint to be visited is the same as the current location, that checkpoint's visit is also completed immediately.
    由于高桥从地点 \(1\) 开始,如果 \(P_1 = 1\),则在出发时即完成对检查点 \(P_1\) 的访问。一般来说,当一个检查点访问完成时,如果下一个要访问的检查点与当前位置相同,则该检查点的访问也立即完成。
  • Even if you pass through a location of a checkpoint whose turn has not yet come, that checkpoint is not completed. You must either already be at that location when its turn comes, or revisit it.
    即使你经过了一个尚未轮到访问的检查点所在地点,该检查点也不会被完成。你必须在轮到时已经位于该地点,或重新访问它。
  • The designated checkpoint numbers are not necessarily distinct from each other, and may coincide with location \(1\) or location \(N\).
    指定的检查点编号不一定互不相同,并且可能与地点 \(1\) 或地点 \(N\) 重合。

Throughout the entire journey, you may pass through the same location or the same road any number of times.
在整个旅程中,你可以多次经过同一地点或同一条道路。

Since Takahashi aims to win, he wants to reach the goal in the shortest possible time. Find the minimum total travel time (in minutes) to depart from location \(1\), visit \(P_1, P_2, \ldots, P_K\) in order, and then arrive at location \(N\). If no valid route exists, output -1.
由于高桥志在取胜,他希望以最短的可能时间到达终点。求从地点 \(1\) 出发,按顺序访问 \(P_1, P_2, \ldots, P_K\),然后到达地点 \(N\) 的最小总旅行时间(以分钟为单位)。如果没有有效的路线,输出 -1

【输入】

\(N\) \(M\) \(K\)
\(U_1\) \(V_1\) \(T_1\)
\(U_2\) \(V_2\) \(T_2\)
\(\vdots\)
\(U_M\) \(V_M\) \(T_M\)
\(P_1\) \(P_2\) \(\ldots\) \(P_K\)

  • The first line contains the number of locations \(N\), the number of roads \(M\), and the number of checkpoints to visit \(K\), separated by spaces.
  • The following \(M\) lines each contain integers \(U_i, V_i, T_i\) separated by spaces, indicating that the \(i\)-th road \((1 \leq i \leq M)\) bidirectionally connects location \(U_i\) and location \(V_i\) and takes \(T_i\) minutes to traverse.
  • The last line contains the \(K\) checkpoint numbers \(P_1, P_2, \ldots, P_K\) separated by spaces. They must be visited in this order.

【输出】

Output in a single line the minimum total travel time (in minutes) as an integer to depart from location \(1\), visit \(P_1, P_2, \ldots, P_K\) in order, and arrive at location \(N\). If no valid route exists, output -1.

Note that the answer may exceed \(10^{10}\).

【输入样例】

5 6 2
1 2 3
2 3 4
3 5 2
1 4 10
4 5 1
2 4 5
3 4

【输出样例】

11

【核心思想】

  1. 问题分析:给定无向带权图,需要从起点 \(1\) 出发,按顺序经过 \(K\) 个检查点 \(P_1, P_2, \ldots, P_K\),最终到达终点 \(N\)。求总路径长度最小值。问题可分解为多个最短路径子问题:\(1 \to P_1 \to P_2 \to \cdots \to P_K \to N\)

  2. 算法选择

    • Dijkstra算法:基于贪心的单源最短路径算法,适用于非负权边
    • 多源最短路分解:将原问题分解为 \(K+1\) 段最短路径问题
  3. 关键步骤

    • 构建无向图邻接表,存储边 \((U_i, V_i, T_i)\)
    • 定义路径序列:\(p[0] = 1\), \(p[1..K]\) 为检查点,\(p[K+1] = N\)
    • 对于每一对相邻节点 \(p[i]\)\(p[i+1]\),运行 Dijkstra 算法求最短路
    • 累加各段最短距离得到总答案 \(ans = \sum_{i=0}^{K} dist(p[i], p[i+1])\)
    • 若任意一段不可达(距离 \(\geq INF/2\)),输出 \(-1\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O((K+1) \cdot (M \log N))\),共运行 \(K+1\) 次 Dijkstra,每次 \(O(M \log N)\)
    • 空间复杂度:\(O(N + M)\),存储邻接表、距离数组和标记数组
  5. 多段最短路径的核心思想

    • 顺序约束问题可分解为多个独立的最短路径子问题
    • 每段子路径相互独立,总最短路等于各段最短路之和
    • 适用于有明确必经点顺序的路径规划问题
    • 注意检查点可能重合(\(P_i = P_{i+1}\))或等于起点/终点的情况

【解题思路】

【算法标签】

Dijkstra

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 50005, M = 100005, INF = 1e18;
// 求1号点到n号点的最短距离,如果不存在,则返回-1
// 链式前向星
int n, m, k, p[N], ans;  // n: 点数,m: 边数,k: 特殊点数(不包含起点和终点),p: 特殊点序列,ans: 总路径长度
int h[N], w[M * 2], e[M * 2], ne[M * 2], idx;  // 邻接表存储图
int dist[N];  // 存储从源点到所有点的最短距离
bool st[N];  // 标记节点是否已确定最短距离
typedef pair<int, int> PII;
priority_queue<PII, vector<PII>, greater<PII> > heap;  // 最小堆,存储(距离, 节点)

// 添加一条从a到b,边权为c的无向边
void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

// Dijkstra算法,计算从源点u到所有点的最短距离
void dijkstra(int u)
{
    // 初始化距离数组
    for (int i = 1; i <= n; i++)
    {
        dist[i] = INF;  // 初始化为无穷大
    }
    memset(st, 0, sizeof(st));  // 重置标记数组
    while (!heap.empty())  // 清空优先队列
    {
        heap.pop();
    }

    dist[u] = 0;  // 源点距离为0
    heap.push({0, u});  // 将源点加入堆

    while (!heap.empty())
    {
        auto t = heap.top();
        heap.pop();
        int veid = t.second, distance = t.first;  // 当前最短距离节点

        if (st[veid] == true)  // 如果已确定最短距离,跳过
        {
            continue;
        }
        st[veid] = true;  // 标记为已确定

        // 遍历当前节点的所有邻接点
        for (int i = h[veid]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i])  // 如果能通过当前节点更新到j的距离
            {
                dist[j] = distance + w[i];  // 更新距离
                heap.push({dist[j], j});  // 加入堆
            }
        }
    }
}

signed main()
{
    cin >> n >> m >> k;  // 读入点数、边数、特殊点数
    memset(h, -1, sizeof(h));  // 初始化邻接表

    // 读入无向图的边
    for (int i = 1; i <= m; i++)
    {
        int u, v, t;
        cin >> u >> v >> t;
        add(u, v, t), add(v, u, t);  // 无向边,添加两次
    }

    // 读入k个特殊点
    for (int i = 1; i <= k; i++)
    {
        cin >> p[i];
    }

    p[0] = 1;  // 起点设为1
    p[++k] = n;  // 终点设为n,现在p[0..k]表示完整的路径序列

    // 计算从p[0]到p[1], p[1]到p[2], ..., p[k-1]到p[k]的最短距离
    for (int i = 0; i < k; i++)
    {
        if (p[i] == p[i + 1])  // 如果起点和终点相同,跳过
        {
            continue;
        }

        dijkstra(p[i]);  // 从p[i]出发求最短路

        if (dist[p[i + 1]] >= INF / 2)  // 如果不可达
        {
            cout << -1 << endl;
            return 0;
        }

        ans += dist[p[i + 1]];  // 累加路径长度
    }

    cout << ans << endl;  // 输出总路径长度
    return 0;
}

【运行结果】

5 6 2
1 2 3
2 3 4
3 5 2
1 4 10
4 5 1
2 4 5
3 4
11
posted @ 2026-06-27 14:19  团爸讲算法  阅读(7)  评论(0)    收藏  举报