1. 杭电OJ 2066题解析:从题目理解到AC代码
杭电OJ(Hangzhou Dianzi University Online Judge)作为国内知名的程序设计竞赛练习平台,2066题是其中一道经典的图论题目。这道题主要考察对Dijkstra算法的理解和应用能力,同时也需要处理一些特殊的边界条件。
1.1 题目重述与分析
题目描述大致是这样的:小明的家附近有多个城市,他想去某些特定的城市旅游。现在给出城市之间的道路连接情况和时间消耗,要求找出从家到目标城市的最短时间。
关键信息点:
- 城市编号从1开始
- 可能存在多个起点(家附近的城市)
- 有多个目标城市
- 需要找出所有可能路径中的最短时间
输入格式通常包含:
- 道路数量T
- 接下来T行,每行三个数字表示城市A到城市B需要的时间C
- 起点城市数量S
- S个起点城市编号
- 目标城市数量D
- D个目标城市编号
1.2 解题思路与算法选择
这道题的核心是典型的最短路径问题,但有几点特殊之处需要考虑:
- 多起点问题:不像传统的最短路径问题只有一个起点,这里有多个可能的起点
- 多终点问题:同样有多个可能的终点
- 需要找出所有起点到所有终点的最短路径中的最小值
对于这种情况,我们有几种可能的解决思路:
- 对每个起点单独运行Dijkstra算法,然后比较所有结果
- 使用虚拟超级源点连接所有起点,然后运行一次Dijkstra
- 使用SPFA或其他最短路径算法
经过分析,第二种方法效率最高,只需要运行一次Dijkstra算法即可。具体实现方法是:
- 创建一个虚拟节点0
- 从0到所有起点建立边,边权为0
- 然后从0开始运行Dijkstra算法
- 最后检查所有终点的距离,取最小值
1.3 代码实现与优化
基于上述思路,我们可以写出如下C++代码框架:
cpp复制#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
const int MAXN = 1005;
vector<pair<int, int>> adj[MAXN];
int dist[MAXN];
void dijkstra(int start) {
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
fill(dist, dist + MAXN, INT_MAX);
dist[start] = 0;
pq.push({0, start});
while (!pq.empty()) {
int u = pq.top().second;
int d = pq.top().first;
pq.pop();
if (d > dist[u]) continue;
for (auto &edge : adj[u]) {
int v = edge.first;
int w = edge.second;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
}
int main() {
int T, S, D;
while (cin >> T >> S >> D) {
for (int i = 0; i < MAXN; i++) adj[i].clear();
for (int i = 0; i < T; i++) {
int a, b, c;
cin >> a >> b >> c;
adj[a].push_back({b, c});
adj[b].push_back({a, c});
}
// 添加超级源点0
for (int i = 0; i < S; i++) {
int s;
cin >> s;
adj[0].push_back({s, 0});
}
dijkstra(0);
int min_dist = INT_MAX;
for (int i = 0; i < D; i++) {
int dest;
cin >> dest;
min_dist = min(min_dist, dist[dest]);
}
cout << min_dist << endl;
}
return 0;
}
1.4 常见问题与调试技巧
在实际解决这道题时,可能会遇到以下几个常见问题:
-
数组越界:城市编号从1开始,但有些同学可能会错误地认为从0开始
- 解决方法:仔细阅读题目描述,确认编号范围
- 调试技巧:添加边界检查代码
-
多组测试数据未清空:杭电OJ的题目通常是多组测试数据
- 解决方法:每组数据开始前清空邻接表和距离数组
- 调试技巧:在本地用多组数据测试
-
优先级队列使用不当:Dijkstra算法需要使用最小堆
- 解决方法:确保priority_queue的正确声明方式
- 调试技巧:打印队列内容检查顺序
-
无穷大值设置不当:INT_MAX在某些情况下可能导致溢出
- 解决方法:使用0x3f3f3f3f作为无穷大值
- 调试技巧:检查中间计算结果
-
输入格式处理错误:题目输入可能有多个测试用例
- 解决方法:使用while(cin >> T >> S >> D)处理输入
- 调试技巧:在本地用文件重定向测试
1.5 性能优化与进阶思考
对于这道题,还可以考虑以下优化方向:
- 堆优化:使用更高效的堆实现,如Fibonacci堆
- 输入优化:使用更快的输入方法,如scanf或快速读取函数
- 邻接表优化:使用更紧凑的邻接表存储方式
- 提前终止:当找到所有目标城市的最短路径后可以提前终止
此外,这道题还可以扩展到以下变种:
- 限制路径长度或中转次数
- 考虑路径上的其他约束条件
- 多目标优化(如同时考虑时间和费用)
提示:在实际竞赛中,建议先写出正确但可能不够优化的代码,确保AC后再考虑优化。过早优化可能导致难以发现的错误。
1.6 个人实战经验分享
在解决这道题的过程中,我总结了以下几点经验:
- 仔细阅读题目:确认输入输出格式和边界条件
- 画图辅助理解:对于图论问题,画出样例图有助于理解
- 模块化编程:将Dijkstra算法封装成函数,便于调试和重用
- 测试用例设计:设计包含边界条件的测试用例,如单城市、最大数据量等
- 调试输出:在关键位置添加临时输出语句,帮助定位问题
对于初学者,建议从以下几个方面提升:
- 熟练掌握Dijkstra算法的原理和实现
- 理解STL容器的使用,特别是priority_queue
- 练习图论问题的建模能力
- 培养调试技巧和耐心
这道题虽然不算很难,但很好地考察了对最短路径算法的理解和应用能力。通过这道题,可以加深对Dijkstra算法及其变种的理解,为后续更复杂的图论问题打下基础。
