1346 字
7 分钟
P1266 速度限制
1.[题目概述](P1266 速度限制 - 洛谷)
- 你现在从 0 号点 走向 目标点D ,问你最快的移动路径是什么,约束条件是:
-
- 每条路径都可能有限速(如果没有则按照进入速度计算)
-
- 初始速度是 70
-
- 要求计算的是 移动的路径(因此得存储移动的路径,通过逆向回溯查询) 关键约束:
-
- 进入 每一个没有限度路段的路,速度都可能不同,得进行状态转移
2. 核心思路阐述
解题思路
- 由于进入 每一个未限速路段的速度都可能不同,因此,通过常规的 dijk 无法直接写,可以通过状态转移的方式去写:即 表达 ”当前用 v 速度 从 i 通向 j“ 所消耗的最短时间.
- 最后用dijk 计算出 所有以 任意速度 进入 D 点的最快时间.
- 遍历 所有 找出最少时间 的 v, 利用 逆向回溯 找到所有路径。
关键观察
- 由于 从任意一段路径 经过 未限速路段速度可能不相同,故只能通过状态转移的方式去写
- 又因为 数据量小 我们可以直接存储所有 速度 来转移计算
算法选择
- 最开始有想过直接 用原有速度 和 时间去覆盖答案, 但出现的问题是:不同速度进入的时间不同,且最后的答案也不相同,可能我速度慢 时间快, 但最后进入 D 点的时间就会慢, 反之,如果我速度快,时间慢,进入D点的时间不一定慢(理解为中间加速了)
- 属性不同不能直接覆盖,需要扩充成属性相同的元素 进行转移
- [[dijkstra]] + dp (其实真要说 就是DP写,dijk 是 dp的变种)
- 如果本题的路径是 拓扑序 则可以直接用 dp 来写
3. 详细算法设计
算法步骤
- 先建图: 将 存入图中
- 初始化 为 , 且 (从 0 点 速度 70 出发)
- 优先队列存储 三个数据 (到点u 的最少时间,点u, 出点u的速度)
- dijk 跑最短路(需要存储 u 点 速度 v 是从 哪来的)
- 通过回溯,得出所有的路径.
4. 正确性证明
正确性证明
- 所有的路径都是 从 转移过来的, 并且考虑所有 进入的速度(只保留进入速度的最大值)
- 最后遍历所有进入 D 点的时间 v 找到 最少的时间,就是所有情况最快的时间
5. 复杂度分析
-
时间复杂度:
- 总共 个路径 个点,且每个点状态有 个速度
-
空间复杂度:
- 所有情况
6. 代码实现
#include <bits/stdc++.h>using namespace std;#define int long longusing PII = pair<int, int>;using PDII = pair<double,PII>;
const int N = 160,INF = 1e9, M = N * N * 2;
/*本题出现的一大难点: 1. 有速度限制,并且部分没有速度的路径需要用上一段结束的速度并保持(这里有点像动态规划转移的思想)
*/
class cmp{public: bool operator()(PDII a, PDII b){ return a.first > b.first; }};
int n, m;int h[N], ne[M], e[M], v[M], s[M], idx;int st[N][510]; //表示 到 i 点速度 j 是否被访问过double dist[N][510]; // 表示 到 i 点速度 j 的最短时间PII last[N][510];
void add(int a, int b, int c, int d){ e[idx] = b, v[idx] = c, s[idx] = d, ne[idx] = h[a], h[a] = idx++;}
void show(int u, int v){ if(u == 0) return; show(last[u][v].first, last[u][v].second); cout << " " << u ;}
void dijk(int ed){ for(int i = 0; i <= n; i++){ for(int j = 0; j < 510; j++){ dist[i][j] = INF; } } dist[0][70] = 0; priority_queue<PDII, vector<PDII>, cmp> heap; heap.push({0, {0, 70}}); while(heap.size()){ auto t = heap.top(); heap.pop(); int ver = t.second.first, speed = t.second.second; double dis = t.first; if(st[ver][speed]) continue; st[ver][speed] = 1; for(int i = h[ver]; i != -1; i = ne[i]){ int j = e[i]; int nv = v[i], ns = s[i]; if(nv){ if(dist[j][nv] > dis + ns * 1.0 / nv){ dist[j][nv] = dis + ns * 1.0 / nv; last[j][nv] = {ver, speed}; heap.push({dist[j][nv], {j, nv}}); } }else{ nv = speed; if(dist[j][nv] > dis + ns * 1.0 / nv){ dist[j][nv] = dis + ns * 1.0 / nv; last[j][nv] = {ver, speed}; heap.push({dist[j][nv], {j, nv}}); } } } }
int mv = 0; for(int i = 0; i < 510; i++){ if(dist[ed][i] < dist[ed][mv]) mv = i; } //cout << mv << endl; cout << "0"; show(ed, mv); cout << endl;}
void solve(){ int ed; memset(h, -1 ,sizeof h); cin >> n >> m >> ed;
for(int i = 1; i <= m; i++){ int a, b, c, d; cin >> a >> b >> c >> d; add(a, b, c, d); //add(b, a, c, d); } dijk(ed);}
signed main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int _ = 1; while(_--){ solve(); }}7. 常见错误与陷阱
常见错误
- 本题没有什么明显的陷阱,
8. 拓展与变种
相关题目
- 暂无
问题变种
实际应用
9. 总结与收获
总结
- 本题是一道 不错的 最短路变形题
- 利用状态转移,将经过没有限速的路径中的所有情况全部考虑
- 最后算出最短的路径
核心知识点
- dijk + dp
思维模式
- 有 多种状态的表示 情况, 就采取 dp, 又由于他是最短路的题目(并且没有拓扑序) 用 dijk
学习建议
- 如果出现 多种状态的表示 需要转移, 比如当前是 A 状态 转移到 B 状态 则考虑用动态规划去写
部分信息可能已经过时









