当前位置: 首页 > news >正文

信息学奥赛刷题实战:用Dijkstra算法搞定《城市路》这道题(附C++完整代码)

信息学奥赛刷题实战:用Dijkstra算法搞定《城市路》这道题(附C++完整代码)

第一次接触《城市路》这类最短路径题目时,我盯着2000个顶点和10000条边的数据规模发愁——邻接矩阵会爆内存,朴素Dijkstra可能超时,到底该选哪种实现方式?经过多次比赛实战和代码优化,我发现堆优化的Dijkstra配合链式前向星才是这类题目的黄金组合。本文将带你从题目分析到AC代码,一步步拆解如何用不同姿势征服这道经典题目。

1. 题目分析与算法选型

《城市路》作为信息学奥赛经典题型,考察的是单源最短路径问题的实际应用能力。题目给出n个城市(顶点)和m条双向道路(边),要求计算从城市1到城市n的最短距离。表面看是模板题,但数据规模暗藏玄机:

  • 顶点数n=2000:直接排除了O(V²)的邻接矩阵存储
  • 边数m=10000:提示我们需要考虑O(ElogV)级别的算法
  • 双向边:建图时需要正反双向处理

算法选择对比表

算法类型时间复杂度适用场景本题适用性
朴素DijkstraO(V²)稠密图可能超时
堆优化DijkstraO(ElogV)稀疏图★★★★☆
SPFAO(kE)负权边★★★☆☆

提示:虽然SPFA在随机数据下表现优异,但竞赛中更推荐使用稳定的Dijkstra算法

2. 数据结构设计:邻接表的两种实现

面对2000个顶点,我们必须放弃邻接矩阵。以下是两种更优的邻接表实现方案:

2.1 vector数组实现(推荐新手)

struct Edge { int v, w; // 目标顶点和边权值 Edge(int _v, int _w) : v(_v), w(_w) {} }; vector<Edge> graph[N]; // 邻接表存储 // 添加边示例(双向) graph[f].emplace_back(t, w); graph[t].emplace_back(f, w);

优势

  • 代码直观易调试
  • 无需手动管理内存
  • 遍历时直接使用range-based for

2.2 链式前向星实现(竞赛常用)

struct Edge { int to, w, next; } edges[M*2]; // 无向图需双倍空间 int head[N], cnt; void addEdge(int u, int v, int w) { edges[++cnt] = {v, w, head[u]}; head[u] = cnt; } // 添加双向边 addEdge(f, t, w); addEdge(t, f, w);

性能对比

  • 内存占用:链式前向星更节省(固定数组)
  • 访问速度:链式前向星cache命中率更高
  • 编码复杂度:vector更易实现

3. Dijkstra算法的三种实现策略

3.1 朴素版Dijkstra(理解原理)

void dijkstra(int start) { memset(dis, 0x3f, sizeof(dis)); // 初始化为INF dis[start] = 0; for(int i = 1; i <= n; ++i) { int u = -1; // 找到未访问的最近顶点 for(int j = 1; j <= n; ++j) if(!vis[j] && (u == -1 || dis[j] < dis[u])) u = j; vis[u] = true; // 松弛操作 for(auto &e : graph[u]) { int v = e.v, w = e.w; if(!vis[v] && dis[v] > dis[u] + w) dis[v] = dis[u] + w; } } }

缺陷分析

  • 每次查找最小dis需O(V)时间
  • 总复杂度O(V²)在V=2000时约为4e6次操作
  • 实际测试在部分OJ上可能卡在时间边缘

3.2 堆优化版Dijkstra(竞赛首选)

using PII = pair<int, int>; // first:距离 second:顶点 priority_queue<PII, vector<PII>, greater<PII>> pq; void dijkstra(int start) { memset(dis, 0x3f, sizeof(dis)); dis[start] = 0; pq.emplace(0, start); while(!pq.empty()) { auto [d, u] = pq.top(); pq.pop(); if(vis[u]) continue; vis[u] = true; for(auto &e : graph[u]) { int v = e.v, w = e.w; if(dis[v] > dis[u] + w) { dis[v] = dis[u] + w; pq.emplace(dis[v], v); } } } }

关键优化点

  • 使用优先队列快速获取最小dis节点(O(logV))
  • 通过vis数组避免重复处理
  • 总复杂度降为O(ElogV)

3.3 堆优化+链式前向星(终极优化)

void dijkstra(int start) { memset(dis, 0x3f, sizeof(dis)); dis[start] = 0; pq.emplace(0, start); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = true; for(int i = head[u]; i; i = edges[i].next) { int v = edges[i].to, w = edges[i].w; if(dis[v] > dis[u] + w) { dis[v] = dis[u] + w; pq.emplace(dis[v], v); } } } }

性能实测数据(n=2000, m=10000):

实现方式运行时间(ms)内存消耗(MB)
朴素Dijkstra2358.7
堆优化+vector4510.2
堆优化+链式前向星327.1

4. 常见踩坑与调试技巧

4.1 无穷大设置的艺术

// 0x3f3f3f3f的妙处: // 1. 足够大(1061109567) // 2. 两个0x3f相加不会溢出 // 3. memset按字节设置 memset(dis, 0x3f, sizeof(dis));

错误案例

#define INF 0x7fffffff // 可能导致dis[u]+w溢出

4.2 双向边的处理

// 错误:忘记添加反向边 addEdge(f, t, w); // 正确:无向图需双向添加 addEdge(f, t, w); addEdge(t, f, w);

4.3 优先队列的陷阱

// 错误:未使用greater导致大根堆 priority_queue<PII> pq; // 正确:小根堆 priority_queue<PII, vector<PII>, greater<PII>> pq;

4.4 输入输出优化

// 关闭同步流加速IO(需确保不使用C风格IO) ios::sync_with_stdio(false); cin.tie(nullptr); // 对于1e4级别的输入,可节省约200ms

5. 完整AC代码实现

#include <bits/stdc++.h> using namespace std; const int N = 2005, M = 20005; struct Edge { int to, w, next; } edges[M]; int head[N], dis[N], cnt; bool vis[N]; void addEdge(int u, int v, int w) { edges[++cnt] = {v, w, head[u]}; head[u] = cnt; } void dijkstra(int start) { memset(dis, 0x3f, sizeof(dis)); dis[start] = 0; using PII = pair<int, int>; priority_queue<PII, vector<PII>, greater<PII>> pq; pq.emplace(0, start); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = true; for(int i = head[u]; i; i = edges[i].next) { int v = edges[i].to, w = edges[i].w; if(dis[v] > dis[u] + w) { dis[v] = dis[u] + w; pq.emplace(dis[v], v); } } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; while(m--) { int f, t, w; cin >> f >> t >> w; addEdge(f, t, w); addEdge(t, f, w); } dijkstra(1); cout << (dis[n] == 0x3f3f3f3f ? -1 : dis[n]); return 0; }

代码亮点

  1. 链式前向星存储节省内存
  2. 堆优化保证时间复杂度
  3. 输入输出优化提升速度
  4. 处理了不可达情况(输出-1)
http://www.jsqmd.com/news/984772/

相关文章:

  • VMware Horizon连接服务器证书报错?手把手教你用域控CA证书搞定它
  • 2026年绝缘板源头供应企业选择参考:从通用材料到特种应用的全景分析 - 企业推荐官【官方】
  • 郑州闲置黄金变现,合扬高价回收不扣损耗 - 开心测评
  • 告别丑地图!用ArcGIS Pro给你的坐标点数据做个‘美容’(从符号、标注到布局视图)
  • 不止于转换:深入Python脚本,玩转mbtiles与地图瓦片的双向互操作
  • 80G 高频雷达物位计具备哪些产品优势? - 仪表人小余
  • 2026年6月苏州环氧地坪行业研究报告:哪家施工规范质量又好 - GrowthUME
  • 别再被低价忽悠!等速万向节专机选购建议:看这5点,质量售后全搞定 - 品牌推荐大师
  • 2026揭阳市黄金回收全攻略 多家实体门店横向评测附地址避坑指南 - 余生黄金回收
  • 从开发者视角看数据泄露:那些年我们无意中留下的‘社工库’入口
  • 锦州市专业消防管,供暖管、自来水管漏水检测、外网埋地管道测漏、无损定位 - 天堂海洋
  • 2026年成都回头客多的打酒铺,5强实力榜单为你揭秘! - 企业推荐官
  • 第十四届智能车竞赛双车协同完整工程包(Kinetis平台+CAN通信+双车调度逻辑)
  • LOGO设计大赛服务明星评选投票怎么免费做?企业校园通用投票制作教程(强防刷+零广告+数据免费导) - 微信投票小程序
  • 别再死记模板了!从《信息学奥赛一本通》1382题看C++邻接表的两种写法(vector vs 链式前向星)与性能实测
  • 数学建模竞赛必看:微分方程模型怎么选、怎么建?从赛题到论文的避坑指南
  • 2026 无锡卖黄金品牌避坑变现攻略,虚高报价、扣损耗全拆解 - 奢侈品回收评测
  • 2026 沈阳厨卫屋面地下室漏水瓷砖空鼓测评:吉修匠 99.8 分五星榜首 - 吉修匠
  • 别再均匀采样了!手把手教你用PER优先经验回放加速DQN训练(附PyTorch代码)
  • 实体企业GEO,从苏州到金华再到常熟,我更确定GEO适合实体企业 - 招财兔数字员工
  • 2026年橡胶机械隔热板供应商评估:聚焦常州市永诚新材料与行业关键企业 - 企业推荐官【官方】
  • 上饶市自来水管漏水检测,厂区地下管网测漏查漏 市政管道漏水检测 不开挖精准找漏点 - 同城资讯
  • 不用公众号!永久免费无广告,微信小程序1分钟制作朗诵/歌手/书画投票评选|众星评选实测推荐 - 微信投票小程序
  • 北京股权设计梳理服务机构排行:5家专业之选 - 奔跑123
  • 绍兴柯桥手机店哪家好?手机维修哪家实惠最靠谱? - 博客万
  • 国内余氯在仪十大品牌排名 - 仪表人老张
  • 我的团队知识库安全升级记:用Authelia OIDC保护Outline,再也不用担心第三方登录了
  • YOLO v1损失函数保姆级拆解:平方和误差如何‘教’网络做目标检测?
  • 合扬黄金回收|郑州全城上门,实时报价秒到账 - 开心测评
  • Git 每次 Pull 都要输入密码?教你彻底实现免密操作