Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
1346 字
7 分钟
P1266 速度限制

1.[题目概述](P1266 速度限制 - 洛谷)#

  • 你现在从 0 号点 走向 目标点D ,问你最快的移动路径是什么,约束条件是:
      1. 每条路径都可能有限速(如果没有则按照进入速度计算)
      1. 初始速度是 70
      1. 要求计算的是 移动的路径(因此得存储移动的路径,通过逆向回溯查询) 关键约束
  • 进入 每一个没有限度路段的路,速度都可能不同,得进行状态转移

2. 核心思路阐述#

解题思路#

  • 由于进入 每一个未限速路段的速度都可能不同,因此,通过常规的 dijk 无法直接写,可以通过状态转移的方式去写:即dp[j][v]dp[j][v] 表达 ”当前用 v 速度 从 i 通向 j“ 所消耗的最短时间.
  • 最后用dijk 计算出 所有以 任意速度 进入 D 点的最快时间.
  • 遍历 所有 dp[D][v]dp[D][v] 找出最少时间 的 v, 利用 逆向回溯show(D,v)show(D,v) 找到所有路径。

关键观察#

  • 由于 从任意一段路径 经过 未限速路段速度可能不相同,故只能通过状态转移的方式去写
  • 又因为 数据量小 v500v \leq 500 我们可以直接存储所有 速度 来转移计算

算法选择#

  • 最开始有想过直接 用原有速度 和 时间去覆盖答案, 但出现的问题是:不同速度进入的时间不同,且最后的答案也不相同,可能我速度慢 时间快, 但最后进入 D 点的时间就会慢, 反之,如果我速度快,时间慢,进入D点的时间不一定慢(理解为中间加速了)
  • 属性不同不能直接覆盖,需要扩充成属性相同的元素 进行转移
  • [[dijkstra]] + dp (其实真要说 就是DP写,dijk 是 dp的变种)
  • 如果本题的路径是 拓扑序 则可以直接用 dp 来写

3. 详细算法设计#

算法步骤#

  • 先建图: 将 edgea,b,v,sedge_{a, b, v, s} 存入图中
  • 初始化 disdisINFINF, 且dis[0][70]=0dis[0][70] = 0 (从 0 点 速度 70 出发)
  • 优先队列存储 三个数据 heapdis,u,vheap_{dis,u,v} (到点u 的最少时间,点u, 出点u的速度)
  • dijk 跑最短路(需要存储 u 点 速度 v 是从 哪来的) from[newu][newv]=u,vfrom[new_u][new_v] ={u, v}
  • 通过回溯,得出所有的路径.

4. 正确性证明#

正确性证明#

  • 所有的路径都是 从 dis[0][70]dis[0][70] 转移过来的, 并且考虑所有 进入的速度(只保留进入速度的最大值)
  • 最后遍历所有进入 D 点的时间 v 找到 最少的时间,就是所有情况最快的时间

5. 复杂度分析#

  • 时间复杂度

    • 总共 mvm * v 个路径 nn 个点,且每个点状态有 vv 个速度
    • O(nv+nlog(mv))O(n * v + n* \log(m*v))
  • 空间复杂度

    • 所有情况 O(nv)O(n * v)

6. 代码实现#

#include <bits/stdc++.h>
using namespace std;
#define int long long
using 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 状态 则考虑用动态规划去写
P1266 速度限制
https://blog.rookiesama.space/posts/题解/p1266-速度限制/
作者
Rookiesama
发布于
2026-02-06
许可协议
CC BY

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00