2 条题解
-
0
为了方便你构建 OJ(Online Judge)测试数据,我编写了此数据生成器。它包含了标准堆优化 Dijkstra 算法(用于生成
.out)以及针对性数据构造逻辑(用于生成.in)。数据生成策略 (10 个测试点)
- Case 1 (最小边界):。测试起点即终点的情况。
- Case 2 (不连通图):,且 无法到达部分节点,预期输出 -1。
- Case 3 (样例 1):完全还原题目样例。
- Case 4 (长链图):,测试最短路径的最深层级。
- Case 5 (星形图):中心节点 直接连接到所有其他节点。
- Case 6 (全 0 权值):所有边权均为 0,测试算法对零权边的处理。
- Case 7 (极限稠密随机):,测试算法在复杂图下的常数。
- Case 8 (稀疏随机图):。
- Case 9 (最大权值图):所有边权均为 100,构造较长的最短路径。
- Case 10 (完全图子图):随机选取大量边,并保证起点 随机。
数据生成器代码 (C++14)
#include <iostream> #include <fstream> #include <vector> #include <queue> #include <set> #include <string> #include <random> #include <ctime> #include <algorithm> using namespace std; const int INF = 0x3f3f3f3f; // --- 标准堆优化 Dijkstra 解法,用于生成 .out 文件 --- int solve_std(int n, int m, int k, const vector<vector<pair<int, int>>>& adj) { vector<int> dist(n + 1, INF); dist[k] = 0; priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; pq.push({0, k}); while (!pq.empty()) { int d = pq.top().first; int u = pq.top().second; pq.pop(); if (d > dist[u]) continue; for (auto& edge : adj[u]) { int v = edge.first; int w = edge.second; if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push({dist[v], v}); } } } int max_d = 0; for (int i = 1; i <= n; i++) { if (dist[i] == INF) return -1; max_d = max(max_d, dist[i]); } return max_d; } // --- 写入输入输出文件 --- void write_case(int id, int n, int k, const vector<pair<pair<int, int>, int>>& edges) { string in_name = to_string(id) + ".in"; string out_name = to_string(id) + ".out"; int m = edges.size(); ofstream fout(in_name); fout << n << " " << m << " " << k << "\n"; vector<vector<pair<int, int>>> adj(n + 1); for (auto& e : edges) { fout << e.first.first << " " << e.first.second << " " << e.second << "\n"; adj[e.first.first].push_back({e.first.second, e.second}); } fout.close(); ofstream fans(out_name); fans << solve_std(n, m, k, adj) << endl; fans.close(); cout << "Case " << id << " generated: N=" << n << " M=" << m << endl; } int main() { mt19937 rng(time(0)); // Case 1: n=1 边界 write_case(1, 1, 1, {}); // Case 2: 不连通 write_case(2, 2, 2, {{{1, 2}, 10}}); // Case 3: 样例 1 write_case(3, 4, 2, {{{2, 1}, 1}, {{2, 3}, 1}, {{3, 4}, 1}}); // Case 4: 长链图 { vector<pair<pair<int, int>, int>> e; for(int i=1; i<100; i++) e.push_back({{i, i+1}, 1}); write_case(4, 100, 1, e); } // Case 5: 星形图 { vector<pair<pair<int, int>, int>> e; for(int i=2; i<=100; i++) e.push_back({{1, i}, 50}); write_case(5, 100, 1, e); } // Case 6: 全 0 权值 { vector<pair<pair<int, int>, int>> e; for(int i=1; i<100; i++) e.push_back({{i, i+1}, 0}); e.push_back({{100, 1}, 0}); write_case(6, 100, 50, e); } // Case 7: 极限稠密随机 (N=100, M=6000) { vector<pair<pair<int, int>, int>> e; set<pair<int, int>> exists; while(e.size() < 6000) { int u = rng() % 100 + 1, v = rng() % 100 + 1; if(u != v && exists.find({u, v}) == exists.end()) { e.push_back({{u, v}, (int)(rng() % 101)}); exists.insert({u, v}); } } write_case(7, 100, 1, e); } // Case 8: 稀疏随机 { vector<pair<pair<int, int>, int>> e; for(int i=1; i<=150; i++) { int u = rng() % 100 + 1, v = rng() % 100 + 1; if(u != v) e.push_back({{u, v}, (int)(rng() % 101)}); } write_case(8, 100, 1, e); } // Case 9: 大权值 (全部 100) { vector<pair<pair<int, int>, int>> e; for(int i=1; i<=100; i++) for(int j=1; j<=20; j++) { int v = rng() % 100 + 1; if(i != v) e.push_back({{i, v}, 100}); } write_case(9, 100, 1, e); } // Case 10: 随机综合 { int n = 100, m = 3000; vector<pair<pair<int, int>, int>> e; set<pair<int, int>> exists; while(e.size() < m) { int u = rng() % n + 1, v = rng() % n + 1; if(u != v && exists.find({u, v}) == exists.end()) { e.push_back({{u, v}, (int)(rng() % 101)}); exists.insert({u, v}); } } write_case(10, n, (int)(rng() % n + 1), e); } return 0; }生成器设计说明:
- 文件大小优化:
- 最大规模 。每个
.in文件包含约 6000 行三个整数的数据。 - 单行平均约 10 字节,整个文件约 60 KB。
- 10 个测试点总计约 0.6 MB,完全符合“2MB 以内”的要求。
- 最大规模 。每个
- 区分度设计:
- Case 4 & 5:通过特定的拓扑结构测试搜索深度。
- Case 7:测试 算法在边数饱和时的表现。
- Case 2:测试程序是否能正确输出 -1(连通性判定)。
- 计算安全:
- 使用了
std::set确保生成的有向图没有重边,这符合大多数 OJ 对“网络延迟时间”类题目的数据约定。 solve_std采用标准堆优化 Dijkstra,并使用非递归方式防止爆栈,逻辑上避开了除零异常。
- 使用了
- NOI 格式适配:
- 输入格式严格按照 的顺序读入,边权范围严格遵守 。
使用方法:
- 复制上述代码,在支持 C++11 或更高标准的编译器上编译运行。
- 程序会自动在当前文件夹生成
1.in~10.in和对应的1.out~10.out。 - 将这些文件打包上传到你的 OJ 题目后台即可。
-
0
在 NOI 竞赛中,单源最短路问题是图论的核心。针对本题 的小规模数据,有多种算法可以实现。我们将从最易写的全源最短路过渡到标准的堆优化 Dijkstra。
版本一:Floyd-Warshall 算法 ()
思路: 当 较小时(通常 ),Floyd 算法是性价比最高的选择。它通过三重循环,尝试利用每一个点 作为中间跳板来更新点 到点 的距离。
#include <iostream> #include <vector> #include <algorithm> using namespace std; const int INF = 0x3f3f3f3f; // 竞赛常用无穷大,累加不易溢出且可用memset int dist[105][105]; int n, m, k; int main() { ios::sync_with_stdio(false); cin.tie(0); if (!(cin >> n >> m >> k)) return 0; // 1. 初始化:自己到自己为0,其余为无穷大 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) dist[i][j] = 0; else dist[i][j] = INF; } } // 2. 读入边 for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; dist[u][v] = w; // 注意是有向边 } // 3. Floyd 核心:k 必须在最外层 for (int p = 1; p <= n; p++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (dist[i][p] != INF && dist[p][j] != INF) { dist[i][j] = min(dist[i][j], dist[i][p] + dist[p][j]); } } } } // 4. 统计结果 int max_delay = 0; for (int i = 1; i <= n; i++) { if (dist[k][i] == INF) { // 存在节点不可达 cout << -1 << endl; return 0; } max_delay = max(max_delay, dist[k][i]); } cout << max_delay << endl; return 0; }- 时间复杂度:。对于 ,运算量为 ,瞬间完成。
- 空间复杂度:。需要邻接矩阵存储。
版本二:朴素 Dijkstra 算法 ()
思路: 这是针对单源最短路的贪心算法。每次在未访问的节点中选择一个距离起点最近的点,利用它去松弛邻居。 对于稠密图(边数 接近 ),朴素 Dijkstra 往往比堆优化版更快。
#include <iostream> #include <vector> #include <cstring> #include <algorithm> using namespace std; int g[105][105]; int d[105]; bool vis[105]; int n, m, k; int main() { cin >> n >> m >> k; memset(g, 0x3f, sizeof(g)); for (int i = 1; i <= n; i++) g[i][i] = 0; for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; g[u][v] = w; } memset(d, 0x3f, sizeof(d)); d[k] = 0; for (int i = 0; i < n; i++) { int u = -1; // 寻找当前未访问且距离最短的点 for (int j = 1; j <= n; j++) { if (!vis[j] && (u == -1 || d[j] < d[u])) u = j; } if (d[u] == 0x3f3f3f3f) break; vis[u] = true; // 松弛邻居 for (int v = 1; v <= n; v++) { if (g[u][v] != 0x3f3f3f3f) { d[v] = min(d[v], d[u] + g[u][v]); } } } int res = 0; for (int i = 1; i <= n; i++) { if (d[i] == 0x3f3f3f3f) { cout << -1 << endl; return 0; } res = max(res, d[i]); } cout << res << endl; return 0; }- 时间复杂度:。
- 空间复杂度:。
版本三:堆优化 Dijkstra () —— 最优推荐
思路: 在 NOI 竞赛中,这是处理中大型图()的标准模版。利用优先队列(小根堆)维护“最近的点”,将寻找最小值的复杂度从 降至 。
#include <iostream> #include <vector> #include <queue> #include <algorithm> #include <cstring> using namespace std; // 使用结构体存储边 struct Edge { int to, weight; }; // 优先队列节点,距离小的优先级高 struct Node { int u, dist; bool operator>(const Node& other) const { return dist > other.dist; } }; const int INF = 0x3f3f3f3f; vector<Edge> adj[105]; int dist[105]; int main() { int n, m, k; if (!(cin >> n >> m >> k)) return 0; for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; adj[u].push_back({v, w}); } memset(dist, 0x3f, sizeof(dist)); dist[k] = 0; // 堆优化:小根堆 priority_queue<Node, vector<Node>, greater<Node>> pq; pq.push({k, 0}); while (!pq.empty()) { Node top = pq.top(); pq.pop(); int u = top.u; int d = top.dist; // 关键点:如果当前弹出的距离已经不是最新的,直接跳过(懒惰删除) if (d > dist[u]) continue; for (auto& edge : adj[u]) { int v = edge.to; if (dist[u] + edge.weight < dist[v]) { dist[v] = dist[u] + edge.weight; pq.push({v, dist[v]}); } } } int ans = 0; for (int i = 1; i <= n; i++) { if (dist[i] == INF) { cout << -1 << endl; return 0; } ans = max(ans, dist[i]); } cout << ans << endl; return 0; }
复杂度分析思考过程
- 时间复杂度:
- Floyd: 。逻辑简单,适合比赛开头快速写完拿分。
- 堆优化 Dijkstra: $M \log M \approx 6000 \times 13 \approx 7.8 \times 10^4$。这是性能最优解。
- 空间复杂度:
- 邻接矩阵: 个
int,约 40KB。 - 邻接表: ,约 100KB。
- 对于 NOI 常见的 256MB 内存,两者都可以忽略不计。
- 邻接矩阵: 个
时间复杂度优化建议
- 邻接表/前向星:对于边数远大于点数的图,不要用邻接矩阵,否则初始化和遍历会浪费大量时间。
- 快读:在 时,使用
scanf或getchar()自定义快读函数。 - SPFA 算法:如果边权存在负数,Dijkstra 失效,必须使用 SPFA(虽然在特殊构造的图上会退化到 )。
读题关键词总结
- “有向边传递时间”:确定是有向图。
- “从某个节点发出”:确定是单源路径问题。
- “所有节点都收到”:信号是并行的,所以时间取决于最晚到达的节点,即各点最短路径中的最大值。
- “不可达输出-1”:提示必须检查连通性。
- 1
信息
- ID
- 19463
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 2
- 已通过
- 1
- 上传者