当前位置: 首页 > 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.rkmt.cn/news/1496516.html

相关文章:

  • 天津南开区烧烤推荐|无剧本串吧 适合朋友夜宵团建聚 - 速递信息
  • 营口黄金回收全流程高价变现攻略 - 润富黄金回收
  • 告别丑地图!用ArcGIS Pro给你的坐标点数据做个‘美容’(从符号、标注到布局视图)
  • 2026年6月苏州环氧地坪行业研究报告:哪家施工规范质量又好 - GrowthUME
  • 数学建模竞赛必看:微分方程模型怎么选、怎么建?从赛题到论文的避坑指南
  • 上饶市自来水管漏水检测,厂区地下管网测漏查漏 市政管道漏水检测 不开挖精准找漏点 - 同城资讯
  • 实体企业GEO,从苏州到金华再到常熟,我更确定GEO适合实体企业 - 招财兔数字员工
  • 2026年橡胶机械隔热板供应商评估:聚焦常州市永诚新材料与行业关键企业 - 企业推荐官【官方】
  • Git 每次 Pull 都要输入密码?教你彻底实现免密操作
  • 2026年6月常州沙盘模型定制行业研究报告:哪家服务比较优质 - GrowthUME
  • 国内总铅水质在线分析仪十大品牌排名 - 仪表人老张
  • 衡阳闲置黄金变现攻略 2026六大正规回收门店综合测评 - 余生黄金回收
  • 大盘金价同步无锡回收,2026 卖黄金别盲目等高点 - 奢侈品回收评测
  • 山东微程科技:中国 AI 大模型领跑,本地商家的机会在这里
  • 第2章 安装开发环境(DevEco Studio)
  • Edge浏览器上方搜索栏搜索跳转到百度等搜索引擎搜索问题.
  • 117、飞控中的事件驱动编程
  • 【一句话经验】Everything如何精确搜索
  • 人生感悟 --- 职场潜规则 之 催人下班
  • 如何开发一个2048小游戏
  • 2026年1211灭火器回收厂家排行:北京七氟丙烷检测/北京七氟丙烷灭火器回收/合规服务标杆推荐 - 优质品牌商家
  • Outotec HSC Chemistry 9.5.1.5 热化学/冶金热力学计算软件 安装包及安装教程
  • 2025 CSP-J初赛阅读代码解析
  • 2026年口碑好的江门大基数减重/江门健身管理/江门健身口碑排行 - 行业平台推荐
  • 别再乱用v-if了!用Vue3自定义指令优雅实现按钮权限控制
  • Kotlin高阶函数在Android开发中的高级应用:面试指南与最佳实践
  • Qt 5.12.6 在 Windows 10 上安装,为什么我强烈推荐你用 MinGW 而不是 MSVC?
  • 基于 Simulink 的新能源商用车主驱电机弱磁扩速控制策略仿真实战教程
  • Qt 5.12.6在Win10上安装,为什么我建议你选MinGW而不是MSVC?新手避坑指南
  • 搜索技能——anysearch技能