题解:洛谷 P9126 Moo Route II

【题目来源】

洛谷:P9126 [USACO23FEB] Moo Route II S - 洛谷

【题目描述】

注意:本题的时间限制为 4 秒,是默认限制的两倍。

Bessie 正在度假!由于最近的技术进步,Bessie 可以通过先进的航班旅行,这些航班甚至可以进行时间旅行。此外,即使存在多个“平行”的 Bessie 同时出现也不会有任何问题。

在这个国家,有 \(N\) 个机场,编号为 \(1,2,\cdots,N\),以及 \(M\) 条时间旅行航班(\(1 \leq N,M \leq 200000\))。第 \(j\) 条航班从机场 \(c_j\) 在时间 \(r_j\) 起飞,并在时间 \(s_j\) 抵达机场 \(d_j\)\(0 \leq r_j,s_j \leq 10^9\)\(s_j < r_j\) 是可能的)。此外,Bessie 在机场 \(i\) 需要停留 \(a_i\) 时间(\(1 \leq a_i \leq 10^9\))。也就是说,如果 Bessie 乘坐一趟航班在时间 \(s\) 抵达机场 \(i\),她可以转乘一趟从该机场出发的航班,只要该航班的起飞时间 \(r \geq s + a_i\)。需要注意的是,停留时间不会影响 Bessie 抵达某机场的实际时间。

Bessie 从城市 \(1\) 出发,起始时间为 \(0\)。对于从 \(1\)\(N\) 的每个机场,求出 Bessie 最早可以到达该机场的时间。

【输入】

第一行输入包含两个整数 \(N\)\(M\)

接下来的 \(M\) 行描述航班信息。其中第 \(j\) 行包含 \(c_j, r_j, d_j, s_j\),依次表示航班的起飞机场、起飞时间、降落机场和降落时间。\((1 \leq c_j,d_j \leq N, 0 \leq r_j,s_j \leq 10^9)\)

接下来的 \(1\) 行描述每个机场的停留时间。该行包含 \(N\) 个用空格分隔的整数,分别为 \(a_1,\cdots,a_N\)

【输出】

输出共 \(N\) 行。第 \(i\) 行输出 Bessie 最早到达机场 \(i\) 的时间;如果无法到达该机场,输出 \(-1\)

【输入样例】

3 3
1 0 2 10
2 11 2 0
2 1 3 20
10 1 10

【输出样例】

0
0
20

【核心思想】

  1. 问题分析:给定 \(N\) 个机场、\(M\) 条时间旅行航班(可回到过去),每个机场有停留时间 \(a_i\)。Bessie 从机场 \(1\) 时间 \(0\) 出发,求到达每个机场的最早时间。航班可时间旅行(\(s_j < r_j\)),且允许多个"Bessie"同时存在。本质上是带时间约束的图搜索 + 贪心处理问题:按起飞时间从大到小处理航班,利用时间旅行的特性递归更新可达状态。

  2. 算法选择

    • 按起飞时间排序:每个机场的航班按 leave 升序排序,便于二分或顺序处理
    • 逆序贪心 DFS:从起点出发,对每个机场从后往前扫描航班,利用"时间旅行"特性——到达时间越早,能搭乘的航班越多
  3. 关键步骤

    • 读取数据:读入 \(N, M\)\(M\) 条航班信息,以及 \(N\) 个停留时间 \(a_i\)
    • 初始化:每个机场的航班列表按 leave 升序排序,dis[i] = $\infty$pos[i] 初始化为最后一个航班索引
    • DFS 从机场 \(1\) 出发
      • 对当前机场 \(u\),从 pos[u] 往前遍历航班
      • nxt.leave < t,终止(当前时间已无法搭乘更早起飞的航班)
      • 更新 dis[nxt.des] = min(dis[nxt.des], nxt.arrive)
      • pos[u] = i - 1(标记该航班已处理)
      • 递归 dfs(nxt.des, nxt.arrive + wait[nxt.des])
    • 输出dis[i] = $\infty$ 输出 \(-1\),否则输出 dis[i]
  4. 时间/空间复杂度

    • 时间复杂度:\(O(M \log M)\),排序主导,每个航班最多被处理一次
    • 空间复杂度:\(O(N + M)\),邻接表和数组存储
  5. 逆序贪心 DFS 的核心思想

    • 时间旅行的利用:航班可回到过去,意味着到达时间越早反而能搭乘更多航班(包括过去起飞的),因此从后往前扫描航班是有效的
    • 航班的单调性:按 leave 排序后,一旦 leave < t,后续所有航班都无法搭乘,可直接终止
    • pos 数组的剪枝pos[u] 记录已处理的航班位置,避免重复扫描,保证每个航班只被考虑一次
    • 最早到达的更新dis 数组维护到达每个机场的最早时间,取最小值
    • 停留时间的处理:到达后需等待 \(a_i\) 时间才能转乘,递归时传入 arrive + wait
    • 适用于时间旅行、航班调度、带约束图搜索类问题

【算法标签】

普及+ #DFS-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int maxN = 2e5 + 5;  // 最大城市数量

// 航班信息结构体
struct Fly
{
    int leave;    // 航班离开时间
    int des;      // 航班目的地
    int arrive;   // 到达目的地时间

    // 重载小于运算符,用于排序
    bool operator < (Fly other)
    {
        return leave < other.leave;
    }
};

vector<Fly> v[maxN];  // 每个城市的航班列表
int wait[maxN];       // 每个城市的最小等待时间
int dis[maxN];        // 到达每个城市的最早时间
int pos[maxN];        // 记录每个城市当前处理的航班索引

/**
 * 深度优先搜索函数
 * @param u 当前所在城市
 * @param t 当前时间
 */
void dfs(int u, int t)
{
    // 从后往前处理当前城市的航班
    for (int i = pos[u]; i >= 0; i--)
    {
        Fly nxt = v[u][i];
        // 如果航班离开时间早于当前时间,跳过
        if (nxt.leave < t) break;

        // 更新到达目的地的最早时间
        dis[nxt.des] = min(dis[nxt.des], nxt.arrive);
        pos[u] = i - 1;  // 更新当前城市处理的航班索引

        // 递归处理目的地城市,考虑等待时间
        dfs(nxt.des, nxt.arrive + wait[nxt.des]);
    }
}

int main()
{
    int n, m;  // 城市数量和航班数量
    cin >> n >> m;

    // 输入航班信息
    int from;
    Fly fly;
    for (int i = 1; i <= m; i++)
    {
        cin >> from >> fly.leave >> fly.des >> fly.arrive;
        v[from].push_back(fly);
    }

    // 初始化数据
    for (int i = 1; i <= n; i++)
    {
        cin >> wait[i];  // 输入每个城市的最小等待时间
        dis[i] = 1e9;    // 初始化到达时间为无穷大
        pos[i] = v[i].size() - 1;  // 初始化处理索引为最后一个航班
        sort(v[i].begin(), v[i].end());  // 按离开时间排序航班
    }

    dis[1] = 0;  // 起点城市到达时间为0
    dfs(1, 0);   // 从起点城市开始搜索

    // 输出结果
    for (int i = 1; i <= n; i++)
    {
        cout << (1e9 == dis[i] ? -1 : dis[i]) << endl;
    }

    return 0;
}

【运行结果】

3 3
1 0 2 10
2 11 2 0
2 1 3 20
10 1 10
0
0
20
posted @ 2026-08-17 22:49  团爸讲算法  阅读(7)  评论(0)    收藏  举报