2 条题解

  • 0
    @ 2026-3-26 11:34:40

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

    这个程序将按照 NOI 竞赛的标准,生成 10 组测试点。设计思路是:前两组数据极小,暴力可过;中间增加边界条件;后四组数据达到极限规模,必须使用 O(NlogM)O(N \log M) 的二分答案算法。

    一、 测试点设计说明

    测试点 nn 规模 hh 规模 piles[i]piles[i] 规模 特点
    1-2 100 200 100 极小规模,暴力算法可通过
    3-4 1,000 2,000 10510^5 中等规模,暴力算法开始超时
    5 10410^4 nn 10910^9 边界条件h=nh = n,答案必为 max(piles)\max(piles)
    6 10910^9 边界条件hh 极大,答案必为 11
    7 2n2n 特殊情况:所有元素相等,测试计算精度
    8-10 10810^8 极限数据:大数值随机分布

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

    该程序采用 C++14 标准。它会自动运行标程逻辑并产生对应的 .in.out 文件。

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <string>
    #include <random>
    #include <algorithm>
    #include <ctime>
    
    using namespace std;
    
    typedef long long ll;
    
    // --- 标程:最优解 (二分答案) ---
    bool check(ll k, ll h, int n, const vector<int>& p) {
        if (k == 0) return false;
        ll total = 0;
        for (int i = 0; i < n; i++) {
            // 向上取整公式,防止浮点误差
            total += (p[i] + k - 1) / k;
            // 剪枝优化:如果当前小时数已经超过 h,直接返回失败,防止 long long 溢出
            if (total > h) return false;
        }
        return total <= h;
    }
    
    int solve(int n, ll h, const vector<int>& p) {
        int max_val = 0;
        for (int x : p) if (x > max_val) max_val = x;
    
        int left = 1, right = max_val, ans = max_val;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (check(mid, h, n, p)) {
                ans = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }
    
    // --- 数据构造器 ---
    void generate_test_cases() {
        mt19937 rng(time(0));
    
        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;
            ll h;
            int max_p_range;
    
            // 根据测试点设计矩阵设定范围
            if (t <= 2) { n = 100; h = 200; max_p_range = 100; }
            else if (t <= 4) { n = 1000; h = 2000; max_p_range = 100000; }
            else { n = 10000; max_p_range = 1000000000; }
    
            if (t == 5) h = n; // 边界:h=n
            else if (t == 6) h = 1000000000; // 边界:h极大
            else if (t > 6) h = n + rng() % 100000000;
    
            vector<int> piles(n);
            if (t == 7) { // 边界:全相等
                int val = rng() % 1000000000 + 1;
                piles.assign(n, val);
                h = 2 * n;
            } else {
                for (int i = 0; i < n; i++) {
                    piles[i] = rng() % max_p_range + 1;
                }
            }
    
            // 1. 写入输入文件 (NOI 格式)
            fout << n << " " << h << "\n";
            for (int i = 0; i < n; i++) {
                fout << piles[i] << (i == n - 1 ? "" : " ");
            }
            fout << endl;
    
            // 2. 运行标程并写入输出文件
            int result = solve(n, h, piles);
            fans << result << endl;
    
            cout << "Generated Case " << t << ": n=" << n << " h=" << h << " ans=" << result << endl;
    
            fout.close();
            fans.close();
        }
    }
    
    int main() {
        cout << "Starting data generation for [koko]..." << endl;
        generate_test_cases();
        cout << "All test cases generated successfully." << endl;
        return 0;
    }
    

    三、 技术说明与优化细节

    1. 文件大小控制

      • 本题最大的数据规模是 n=104n=10^4,即使每个香蕉堆都是 10910^9(10位数字),加上空格,每一行大约只有 110,000 字节(约 107 KB)。
      • 10 组输入输出文件的总大小约为 1.1 MB,完全符合你要求的 2MB 以内的理想值。
    2. 安全计算与剪枝

      • 防止除以零:在二分查找中,速度 kk11 开始,确保 check 函数中不会出现 x / 0
      • 防止溢出:在 check 函数累加小时数时,采用了 long long。并且加入了一行关键剪枝:if (total > h) return false;。这在 hh 较小而 kk 很小时,能防止 total 累加到超出 long long 范围。
    3. 区分度设计

      • 暴力解法:如果选手直接从速度 11 开始枚举,时间复杂度是 O(Nmax(Piles))O(N \cdot \max(Piles))。在测试点 3 以后,由于 max(Piles)\max(Piles) 达到 10510^5 甚至 10910^9,暴力算法会因 104×109=101310^4 \times 10^9 = 10^{13} 次运算而彻底卡死。
      • 对拍建议:如果你作为教练需要测试选手的代码,请确保时限(Time Limit)设为 1.0s
    4. 非递归实现

      • 所有的生成逻辑和标程逻辑均采用迭代(Iterative)写法,避免了在处理大规模数据时产生不必要的栈开销。

    四、 如何使用

    1. 将上述代码存为 gen.cpp 并编译:g++ gen.cpp -o gen -std=c++14
    2. 执行 ./gen,它会在当前目录下生成 1.in, 1.out ... 10.out
    3. 将这些文件打包上传到你的 OJ 系统即可。

    信息

    ID
    19501
    时间
    1000ms
    内存
    128MiB
    难度
    10
    标签
    递交数
    5
    已通过
    2
    上传者