1 条题解
-
0
根据提供的标程思路,下面给出本题的详细题解。
题意简述
给定一张 个节点、 条边的有向图(或无向图,按有向处理)。抢劫犯从节点 出发,要逃往节点 。节点 是警察局。
- 对每条边 ,抢劫犯通过需要 时间。
- 市民/警察通过边 需要 时间。
- 每个节点 上都有市民,一旦抢劫犯到达 ,市民就会沿 权最短路跑向警察局 报警。
- 警察在接到报警后会立刻从 出发,沿 权最短路追捕。若警察在某节点(或边上)不晚于抢劫犯到达,则抢劫犯被抓。
问抢劫犯能否在 不被抓住 的前提下到达节点 。
核心思路
1. 预处理市民报警时间
首先以 为源点,边权为 ,执行经典 Dijkstra,得到每个节点 到 的最短时间 。它表示:若有市民从 出发跑向警察局,将在 时间后报警;同时也表示警察从 赶到 的最短时间。
2. 抢劫犯的状态定义
令 为抢劫犯已经消耗的时间(从 出发开始计), 为 当前已知警察最早被通知的时刻。
定义状态 ,其中
的含义:若 ,表示距离警察接到通知还剩 时间;若 ,表示警察已接到通知且已过去了 时间。
初始状态:
抢劫犯在时刻 位于 ,市民从 报警需 时间,因此 ,状态为 。
3. 状态转移
从状态 出发,沿着边 走到 ,边上的抢劫犯用时为 。此时:
- 当前时间变为 ;
- 节点 上的市民会在 时刻报警,故最早通知时刻更新为$$T_{\text{warn}}' = \min\!\big(T_{\text{warn}},\ T_{\text{cur}}' + d_v\big). $$
新状态的 为:
$$t' = T_{\text{cur}}' - T_{\text{warn}}' = \max\!\big(T_{\text{cur}}' - T_{\text{warn}},\ -d_v\big). $$由于 ,得到转移公式:
这个式子体现了两个下界:
- :之前的最早通知时刻在走过该边后继续作用;
- :新市民可能更早报警,使通知时刻提前。
4. 修改的 Dijkstra 过程
我们维护一个优先队列,按 值 从小到大 取出状态(即通知时刻相对当前时间差最小的状态优先处理)。算法流程如下:
- 初始化优先队列,压入 。
- 每次取出队首状态 :
- 若 已被访问过,跳过;
- 若 ,表示警察到达 的时刻 ,即警察能在 点抓住抢劫犯,该状态无效,跳过;
- 否则标记 已访问。若 ,则抢劫犯成功逃脱,答案为
YES。
- 对 的每条出边 ,计算 ,并将 压入优先队列。
- 若队列为空仍未访问到 ,输出
NO。
正确性说明:
越小,意味着警察被通知的时刻相对当前时间越晚,对抢劫犯越有利。因此按 从小到大扩展,本质是贪心地保留最大的逃生余量,可以类比最短路的贪心策略。上述剪枝条件 则相当于“若当前状态已无法避免被抓,则不再继续”。5. 复杂度分析
- 第一次 Dijkstra:。
- 第二次修改 Dijkstra:每个节点最多入队一次,每条边最多松弛一次,优先队列操作 。
- 总时间复杂度:$$O\big((n+m)\log(n+m)\big).$$
以下是基于题解思路的完整 C++ 实现。为了便于理解,已加入详细注释。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll INF = 1e18; struct Edge { int to; ll l, r; // 抢劫犯用时l,警察/市民用时r }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m, k; cin >> n >> m >> k; vector<vector<Edge>> adj(n + 1); for (int i = 0; i < m; ++i) { int u, v; ll l, r; cin >> u >> v >> l >> r; adj[u].push_back({v, l, r}); adj[v].push_back({u, l, r}); // 若无向图需此行,有向图请删除 } // ---------- 第一次 Dijkstra:计算市民报警时间 d[u] ---------- vector<ll> d(n + 1, INF); priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> pq; d[k] = 0; pq.push({0, k}); while (!pq.empty()) { auto [dist, u] = pq.top(); pq.pop(); if (dist != d[u]) continue; for (auto &e : adj[u]) { int v = e.to; ll w = e.r; // 市民沿 r 权跑 if (d[v] > d[u] + w) { d[v] = d[u] + w; pq.push({d[v], v}); } } } // ---------- 第二次修改 Dijkstra:抢劫犯逃生 ---------- // 状态: (t, u),t = T_cur - T_warn // 初始状态: T_cur=0, T_warn=d[1] -> t = -d[1] vector<bool> vis(n + 1, false); priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> states; ll t1 = -d[1]; states.push({t1, 1}); bool canEscape = false; while (!states.empty()) { auto [t, u] = states.top(); states.pop(); if (vis[u]) continue; // 如果警察到达时刻 <= 抢劫犯到达时刻,则在此节点被抓,状态无效 if (d[u] <= t) continue; vis[u] = true; if (u == n) { canEscape = true; break; } for (auto &e : adj[u]) { int v = e.to; ll l = e.l; // 转移: t' = max(t + l, -d[v]) ll nt = max(t + l, -d[v]); if (!vis[v]) { states.push({nt, v}); } } } cout << (canEscape ? "YES" : "NO") << "\n"; return 0; }代码要点说明
INF = 1e18,足够大但不会溢出long long。- 第一次 Dijkstra 得到 ,若某节点无法到达 ,则 ,其相反数
-INF在转移中会自动退化为只保留 ,逻辑正确。 - 第二次优先队列按 从小到大弹出,等价于用最贪心的顺序扩展状态。
- 剪枝条件
d[u] <= t精确实现了“警察能在 点抓住抢劫犯则舍弃该状态”的判断。
时间复杂度
两次优先队列操作均为 ,空间 ,符合标程要求。
总结
本题将警察通知时间与抢劫犯行动时间统一到一个差值状态 中,通过一次预处理最短路和一次状态最短路,即可判断是否存在安全逃生路径。关键在于巧妙定义 ,并将市民报警的影响转化为转移式 ,使得问题可以使用优先队列高效求解。
- 1
信息
- ID
- 7111
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 6
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者