题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 个机场、\(M\) 条时间旅行航班(可回到过去),每个机场有停留时间 \(a_i\)。Bessie 从机场 \(1\) 时间 \(0\) 出发,求到达每个机场的最早时间。航班可时间旅行(\(s_j < r_j\)),且允许多个"Bessie"同时存在。本质上是带时间约束的图搜索 + 贪心处理问题:按起飞时间从大到小处理航班,利用时间旅行的特性递归更新可达状态。
-
算法选择:
- 按起飞时间排序:每个机场的航班按
leave升序排序,便于二分或顺序处理 - 逆序贪心 DFS:从起点出发,对每个机场从后往前扫描航班,利用"时间旅行"特性——到达时间越早,能搭乘的航班越多
- 按起飞时间排序:每个机场的航班按
-
关键步骤:
- 读取数据:读入 \(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])
- 对当前机场 \(u\),从
- 输出:
dis[i] = $\infty$输出 \(-1\),否则输出dis[i]
-
时间/空间复杂度:
- 时间复杂度:\(O(M \log M)\),排序主导,每个航班最多被处理一次
- 空间复杂度:\(O(N + M)\),邻接表和数组存储
-
逆序贪心 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
浙公网安备 33010602011771号