2 条题解

  • 0
    @ 2026-3-26 12:06:23

    你好!我是金牌教练。为了方便你快速搭建 OJ(在线判题系统)测试环境,我为你编写了这个集成化的数据生成器与标程(Generator & Solver)

    在“寻找峰值”这类题目中,数据构造的关键在于确保 nums[i]nums[i+1]nums[i] \neq nums[i+1],并且通过构造单调序列锯齿序列大规模随机序列来全面测试选手的二分逻辑。

    一、 测试点设计矩阵

    测试点 nn 规模 序列特征 考核重点
    1-2 5105 \sim 10 随机小规模 基础逻辑正确性
    3 1 边界:只有一个元素 边界处理
    4 100 严格单调递增 峰值在最右端 (n1n-1)
    5 严格单调递减 峰值在最左端 (0)
    6 1000 先增后减(山峰形) 峰值在中间
    7 先减后增(山谷形) 峰值在两端
    8 51045 \cdot 10^4 锯齿形(极多峰值) 随机返回任意峰值的稳定性
    9-10 10510^5 极限随机规模 O(logn)O(\log n) 效率与大数据稳定性

    二、 数据生成器 C++ 代码

    该程序采用 C++14 标准。它会自动生成 1.in10.out。为了确保 nums[i]nums[i+1]nums[i] \neq nums[i+1],生成逻辑采用了增量随机法。

    #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;
    }
    

    三、 生成器关键细节分析

    1. 文件大小控制

      • n=105n=10^5 时,每个整数约占 10~11 字节,单个 .in 文件约 1.1MB。
      • 10 个测试点总大小约 5MB。虽然略超“全套 2MB”的理想值,但在 n=105n=10^5 的 NOI 标准下,这是必须的。如果需要极度缩小,可将 n=105n=10^5 的点减少到 1 个。
    2. 强制相邻不等

      • 通过 nums[i] = nums[i-1] + delta(其中 delta1delta \ge 1)来生成数据,完美规避了 nums[i]==nums[i+1]nums[i] == nums[i+1] 的非法情况,确保了二分的单调性判定有效。
    3. 计算稳定性

      • 非递归:标程使用 while 循环实现,杜绝了栈溢出风险。
      • 防溢出:在计算 mid 时使用了 l + (r - l) / 2。虽然下标不会溢出 int,但这是良好的竞赛编程习惯。
    4. 区分算法复杂度

      • O(n)O(n) 的线性扫描在 10510^5 规模下通常耗时在 10ms~30ms。
      • O(logn)O(\log n) 的二分查找耗时趋近于 0ms。
      • 建议:在 OJ 上将此题时限设置为 0.05s (50ms),这样可以精准地强制选手使用 O(logn)O(\log n) 算法,而过滤掉 O(n)O(n) 算法。

    四、 读题关键词总结 (OJ 命题视角)

    1. “时间复杂度 O(logn)O(\log n):这是命题人给出的死命令,意味着线性遍历哪怕能过也是“非标准解”。
    2. “相邻元素不相等”:这是保证“二段性”的救命稻草,没有它,二分将无法通过局部比较判定方向。
    3. nums[1]=nums[n]=nums[-1] = nums[n] = -\infty:隐含了“山脚下必有山顶”的性质,保证了答案一定存在。

    同学,你可以运行这个生成器来检查你自己的代码对各种边界情况(单调序列、单点序列)的处理。加油!

    信息

    ID
    19503
    时间
    1000ms
    内存
    128MiB
    难度
    10
    标签
    (无)
    递交数
    1
    已通过
    1
    上传者