2 条题解
-
0
你好!我是金牌教练。为了方便你快速搭建 OJ(在线判题系统)测试环境,我为你编写了这个集成化的数据生成器与标程(Generator & Solver)。
在“寻找峰值”这类题目中,数据构造的关键在于确保 ,并且通过构造单调序列、锯齿序列和大规模随机序列来全面测试选手的二分逻辑。
一、 测试点设计矩阵
测试点 规模 序列特征 考核重点 1-2 随机小规模 基础逻辑正确性 3 1 边界:只有一个元素 边界处理 4 100 严格单调递增 峰值在最右端 () 5 严格单调递减 峰值在最左端 (0) 6 1000 先增后减(山峰形) 峰值在中间 7 先减后增(山谷形) 峰值在两端 8 锯齿形(极多峰值) 随机返回任意峰值的稳定性 9-10 极限随机规模 效率与大数据稳定性
二、 数据生成器 C++ 代码
该程序采用 C++14 标准。它会自动生成
1.in到10.out。为了确保 ,生成逻辑采用了增量随机法。#include <iostream> #include <fstream> #include <vector> #include <string> #include <random> #include <algorithm> #include <ctime> using namespace std; typedef long long ll; // --- 标程:最优解 O(log n) --- int findPeakStd(int n, const vector<int>& nums) { int l = 0, r = n - 1; while (l < r) { int mid = l + (r - l) / 2; if (nums[mid] > nums[mid + 1]) { r = mid; } else { l = mid + 1; } } return l; } // --- 数据构造工具 --- mt19937 rng(time(0)); // 生成 [min, max] 范围内的随机整数 ll randInt(ll min_v, ll max_v) { return uniform_int_distribution<ll>(min_v, max_v)(rng); } void make_data() { for (int t = 1; t <= 10; t++) { string in_name = to_string(t) + ".in"; string out_name = to_string(t) + ".out"; ofstream fout(in_name); ofstream fans(out_name); int n; if (t <= 2) n = randInt(5, 10); else if (t == 3) n = 1; else if (t <= 5) n = 100; else if (t <= 7) n = 1000; else if (t == 8) n = 50000; else n = 100000; vector<int> nums(n); if (t == 3) { nums[0] = randInt(-1e9, 1e9); } else if (t == 4) { // 严格递增 nums[0] = -1e9 + randInt(0, 1e5); for (int i = 1; i < n; i++) nums[i] = nums[i - 1] + randInt(1, 100); } else if (t == 5) { // 严格递减 nums[0] = 1e9 - randInt(0, 1e5); for (int i = 1; i < n; i++) nums[i] = nums[i - 1] - randInt(1, 100); } else if (t == 6) { // A 字形 int mid = n / 2; nums[0] = randInt(-1e8, 0); for (int i = 1; i <= mid; i++) nums[i] = nums[i - 1] + randInt(1, 100); for (int i = mid + 1; i < n; i++) nums[i] = nums[i - 1] - randInt(1, 100); } else if (t == 7) { // V 字形 int mid = n / 2; nums[0] = randInt(1e8, 2e8); for (int i = 1; i <= mid; i++) nums[i] = nums[i - 1] - randInt(1, 100); for (int i = mid + 1; i < n; i++) nums[i] = nums[i - 1] + randInt(1, 100); } else { // 随机震荡 nums[0] = randInt(-1e8, 1e8); for (int i = 1; i < n; i++) { int delta = randInt(1, 1000); if (rng() % 2) nums[i] = nums[i - 1] + delta; else nums[i] = nums[i - 1] - delta; } } // 写入 .in fout << n << "\n"; for (int i = 0; i < n; i++) { fout << nums[i] << (i == n - 1 ? "" : " "); } fout << "\n"; // 写入 .out (标程跑一遍) int ans_idx = findPeakStd(n, nums); fans << ans_idx << "\n"; cout << "Test Case " << t << " [n=" << n << "] generated." << endl; fout.close(); fans.close(); } } int main() { make_data(); return 0; }
三、 生成器关键细节分析
-
文件大小控制:
- 当 时,每个整数约占 10~11 字节,单个
.in文件约 1.1MB。 - 10 个测试点总大小约 5MB。虽然略超“全套 2MB”的理想值,但在 的 NOI 标准下,这是必须的。如果需要极度缩小,可将 的点减少到 1 个。
- 当 时,每个整数约占 10~11 字节,单个
-
强制相邻不等:
- 通过
nums[i] = nums[i-1] + delta(其中 )来生成数据,完美规避了 的非法情况,确保了二分的单调性判定有效。
- 通过
-
计算稳定性:
- 非递归:标程使用
while循环实现,杜绝了栈溢出风险。 - 防溢出:在计算
mid时使用了l + (r - l) / 2。虽然下标不会溢出int,但这是良好的竞赛编程习惯。
- 非递归:标程使用
-
区分算法复杂度:
- 的线性扫描在 规模下通常耗时在 10ms~30ms。
- 的二分查找耗时趋近于 0ms。
- 建议:在 OJ 上将此题时限设置为 0.05s (50ms),这样可以精准地强制选手使用 算法,而过滤掉 算法。
四、 读题关键词总结 (OJ 命题视角)
- “时间复杂度 ”:这是命题人给出的死命令,意味着线性遍历哪怕能过也是“非标准解”。
- “相邻元素不相等”:这是保证“二段性”的救命稻草,没有它,二分将无法通过局部比较判定方向。
- “”:隐含了“山脚下必有山顶”的性质,保证了答案一定存在。
同学,你可以运行这个生成器来检查你自己的代码对各种边界情况(单调序列、单点序列)的处理。加油!
-
信息
- ID
- 19503
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 1
- 已通过
- 1
- 上传者