2 条题解
-
0
为了方便你快速构建 OJ(Online Judge)测试数据,我编写了一个基于 C++14 标准的自动化数据生成器。它采用了**迭代(非递归)**的 BFS 逻辑来产生标准答案,并针对 的规模设计了多种复杂的图论模型。
1. 数据生成器设计思路
- 测试点场景分布:
- T1 (最小边界):,权值为 0。
- T2-T3 (随机小规模):覆盖基础逻辑。
- T4 (长链结构):构造 的深度路径,测试遍历完整性。
- T5 (全 0 数组):测试多起点目标。
- T6 (不可达陷阱):将数组分为两个孤岛,起点所在的岛没有 0。
- T7 (大循环):构造相互指向的循环跳跃。
- T8 (之字形跳跃):构造跨度极大的左右反复跳跃。
- T9-T10 (最大规模随机):测试 下的性能。
- 安全性:求解器使用
std::queue实现 BFS,绝对不会爆栈。 - 文件大小控制:单个
.in文件约 ,总压缩包非常小。
2. 数据生成器源码 (C++14)
#include <iostream> #include <fstream> #include <vector> #include <queue> #include <random> #include <string> #include <algorithm> using namespace std; // ---------------- 核心算法:求解器用于生成标准答案 (.out) ---------------- bool solve(int n, int start, const vector<int>& arr) { if (arr[start] == 0) return true; queue<int> q; vector<bool> vis(n, false); q.push(start); vis[start] = true; while (!q.empty()) { int u = q.front(); q.pop(); if (arr[u] == 0) return true; int next_pos[2] = {u + arr[u], u - arr[u]}; for (int i = 0; i < 2; ++i) { int v = next_pos[i]; if (v >= 0 && v < n && !vis[v]) { vis[v] = true; q.push(v); } } } return false; } // ---------------- 随机数工具 ---------------- mt19937 rng(1337); int randInt(int l, int r) { return uniform_int_distribution<int>(l, r)(rng); } // ---------------- 构造逻辑 ---------------- void generate_case(int t) { string in_name = to_string(t) + ".in"; string out_name = to_string(t) + ".out"; ofstream fin(in_name); int n, start; vector<int> arr; if (t == 1) { // 最小边界 n = 1; start = 0; arr = {0}; } else if (t == 2) { // 小规模随机 n = 10; start = randInt(0, 9); for(int i=0; i<n; ++i) arr.push_back(randInt(0, 5)); } else if (t == 3) { // 随机不可达 n = 20; start = 0; for(int i=0; i<n; ++i) arr.push_back(randInt(5, 10)); // 跳跃很大,难以踩到0 arr[19] = 0; // 0 在最后 } else if (t == 4) { // 极限长链 (True) n = 50000; start = 0; arr.assign(n, 1); // 每次跳 1 步 arr[n-1] = 0; } else if (t == 5) { // 高密度 0 n = 50000; start = randInt(0, n-1); for(int i=0; i<n; ++i) arr.push_back(randInt(0, 2) == 0 ? 0 : randInt(1, 10)); } else if (t == 6) { // 孤岛陷阱 (False) n = 50000; start = 0; arr.assign(n, 0); for(int i=0; i < n/2; ++i) arr[i] = randInt(1, 10); // 前半段没有0,且跳不出前半段 for(int i=n/2; i < n; ++i) arr[i] = 0; // 0 全部在后半段 } else if (t == 7) { // 循环跳跃 (False) n = 50000; start = 0; arr.assign(n, 2); // 0->2, 2->4... 永远踩不到奇数位的0 for(int i=1; i<n; i+=2) arr[i] = 0; } else if (t == 8) { // 之字形强力跳跃 n = 50000; start = n/2; for(int i=0; i<n; ++i) arr.push_back(randInt(n/4, n/2)); arr[0] = 0; arr[n-1] = 0; } else { // 最大规模随机 n = 50000; start = randInt(0, n-1); for(int i=0; i<n; ++i) arr.push_back(randInt(0, n-1)); if (t == 10) arr[randInt(0, n-1)] = 0; // 确保至少有一个0 } // 写入输入文件 fin << n << " " << start << "\n"; for (int i = 0; i < n; ++i) { fin << arr[i] << (i == n - 1 ? "" : " "); } fin << "\n"; fin.close(); // 生成并写入答案 ofstream fout(out_name); fout << (solve(n, start, arr) ? "true" : "false") << endl; fout.close(); cout << "Testcase " << t << " [N=" << n << "] finished." << endl; } int main() { for (int i = 1; i <= 10; ++i) generate_case(i); return 0; }3. 测试点详细说明
测试点 规模 特征描述 考查点 1 只有 1 个元素且为 0 边界特判 2 10 小规模随机 基础 BFS 路径搜索 3 20 稀疏 0 位 跨度较大的跳跃处理 4 50,000 全为 1 的跳跃链 考查 遍历的完整性 5 极其密集的 0 考查一旦发现目标立即退出的效率 6 分裂的组件 (Unreachable) 考查不可达情况下的 vis数组防死循环能力7 步长为 2 的偶数环 考查逻辑严密性(踩不到奇数位的 0) 8 大跨度左右震荡 考查边界检查 if (v >= 0 && v < n)9 完全随机数据 综合压力测试 10 随机 + 强制 0 存在 4. 教练的优化建议与使用说明
- 内存与效率:对于 的数据,使用
vector<bool>或bool vis[MAXN]仅占用约 内存,效率极高。 - 避免死循环:在生成不连通图(如 T6, T7)时,学生代码如果没有
vis标记,会因在两个下标间无限反复跳跃而导致TLE或MLE。 - OJ 部署:
- 将
.in和.out文件配对上传。 - 设置时间限制为 1.0s(实际 运行只需约 )。
- 设置空间限制为 128MB(实际只需约 )。
- 将
这个生成器产生的输入格式完全符合竞赛要求的
N start后接数组模式,且逻辑严密,适合作为标准的练习题库数据。 - 测试点场景分布:
-
0
在处理“可达性”问题时,搜索算法是核心。对于本题, 的数据规模要求我们必须达到 的时间复杂度。
以下提供三个版本的 C++ 代码:从基础的递归 DFS 到标准 BFS,再到针对竞赛常数优化的版本。
版本一:深度优先搜索 (DFS) 递归版
思路:从
start开始,递归探测两个跳跃方向。使用vis数组记录已访问位置。 易错点:在 NOI 环境下, 的递归深度可能接近系统默认栈限制,需注意。#include <iostream> #include <vector> using namespace std; const int MAXN = 50005; int arr[MAXN]; bool vis[MAXN]; int n; bool dfs(int u) { // 越界检查或已访问检查 if (u < 0 || u >= n || vis[u]) return false; // 目标检查:找到 0 则成功 if (arr[u] == 0) return true; // 标记当前节点已访问,防止死循环 vis[u] = true; // 递归向左和向右跳跃 // 利用逻辑或的短路特性:只要一边找到即可返回 true return dfs(u + arr[u]) || dfs(u - arr[u]); } int main() { int start; if (!(cin >> n >> start)) return 0; for (int i = 0; i < n; i++) cin >> arr[i]; if (dfs(start)) cout << "true" << endl; else cout << "false" << endl; return 0; }
版本二:广度优先搜索 (BFS) 标准版
思路:使用队列按层级扩散。BFS 在判定“是否存在路径”时非常稳健,且没有递归爆栈的风险,是竞赛中最推荐的写法。
#include <iostream> #include <vector> #include <queue> using namespace std; const int MAXN = 50005; int arr[MAXN]; bool vis[MAXN]; int main() { int n, start; // 竞赛建议:使用快速输入输出或 scanf/printf if (!(cin >> n >> start)) return 0; for (int i = 0; i < n; i++) cin >> arr[i]; queue<int> q; q.push(start); vis[start] = true; bool found = false; while (!q.empty()) { int u = q.front(); q.pop(); // 判定是否达到目标 if (arr[u] == 0) { found = true; break; } // 尝试两个方向 int directions[2] = {u + arr[u], u - arr[u]}; for (int i = 0; i < 2; i++) { int v = directions[i]; // 易错点:必须先判断越界,再访问 vis 数组 if (v >= 0 && v < n && !vis[v]) { vis[v] = true; q.push(v); } } } if (found) cout << "true" << endl; else cout << "false" << endl; return 0; }
版本三:竞赛极致优化版 (数组队列 + 快读)
思路:在 较大时,
std::queue的内存分配和cin的效率较低。通过数组模拟队列和手写快读,可以获得最优的常数表现。#include <cstdio> // 手写快速读入 inline int read() { int x = 0; char ch = getchar(); while (ch < '0' || ch > '9') ch = getchar(); while (ch >= '0' && ch <= '9') { x = x * 10 + (ch - '0'); ch = getchar(); } return x; } const int MAXN = 50005; int arr[MAXN]; bool vis[MAXN]; int q[MAXN]; // 手写静态队列,大小为 N 即可 int main() { int n, start; n = read(); start = read(); for (int i = 0; i < n; i++) arr[i] = read(); int head = 0, tail = 0; q[tail++] = start; vis[start] = true; bool found = false; while (head < tail) { int u = q[head++]; if (arr[u] == 0) { found = true; break; } // 向右跳 int v1 = u + arr[u]; if (v1 < n && !vis[v1]) { vis[v1] = true; q[tail++] = v1; } // 向左跳 int v2 = u - arr[u]; if (v2 >= 0 && !vis[v2]) { vis[v2] = true; q[tail++] = v2; } } if (found) printf("true\n"); else printf("false\n"); return 0; }
复杂度分析思考过程
-
时间复杂度分析:
- 遍历逻辑:在搜索过程中,每个下标(节点)最多只会被标记为
vis一次,每个节点最多产生 2 个分支。 - 计算:总计访问的节点数为 ,总边数为 。
- 结论:时间复杂度为 。对于 的规模,运行时间在 1~5ms 之间,远快于竞赛限制。
- 遍历逻辑:在搜索过程中,每个下标(节点)最多只会被标记为
-
空间复杂度分析:
- 存储空间:
arr数组 ,vis数组 。 - 辅助空间:BFS 队列或 DFS 递归栈最坏情况下存储 个元素,空间 。
- 结论:总空间复杂度为 。约占用 1MB 内存,远低于 128MB 的限制。
- 存储空间:
时间复杂度优化建议
- 及早退出:在将节点推入队列(或进入递归)的一瞬间就判断
arr[v] == 0,可以比弹出队列时再判断少运行一轮。 - 内存优化:如果内存限制极其严格(例如只有 2MB),可以利用
arr[i]的正负号来标记visited(例如将arr[i]变为负数),从而省去vis数组。但这种做法在原数组有 0 的情况下需要特殊处理。 - IO 优化:在 NOI 竞赛中,若 ,请务必使用
scanf或快读,cin即使加了sync_with_stdio(false)有时依然不够快。
总结
作为教练,我推荐你在正式考场上优先使用 版本二 (BFS)。它结构清晰,且能有效规避 DFS 可能带来的栈溢出风险,同时通过 的效率稳拿满分。
-
- 1
信息
- ID
- 19456
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 1
- 已通过
- 1
- 上传者