2 条题解

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

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

    这个程序将自动生成 10 组符合 NOI 规范的测试点(1.in/out ~ 10.in/out)。为了体现竞赛的梯度,我对数据进行了精细化分布,以区分暴力枚举优先队列模拟二分答案三种复杂度的解法。

    一、 测试点设计矩阵

    测试点 NN 规模 TotalTripsTotalTrips 规模 特点 预期通过算法
    1-2 100 极小规模 全部算法
    3-4 1,000 10510^5 中等规模 模拟、二分
    5 10510^5 1 边界:只需一趟 二分
    6 1 10710^7 边界:只有一辆车
    7 10510^5 均匀分布:time[i]time[i] 均相同
    8 极限稀疏:time[i]time[i] 极大
    9-10 极限综合随机数据 仅限二分

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

    该程序采用 C++14 标准,包含了生成输入数据和利用标准二分算法生成输出答案的完整逻辑。

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <string>
    #include <random>
    #include <algorithm>
    #include <ctime>
    
    using namespace std;
    
    typedef long long ll;
    
    /**
     * 标准答案:二分查找 + 贪心验证
     * 时间复杂度: O(N log(min_time * totalTrips))
     */
    bool check(ll mid, ll totalTrips, int n, const vector<int>& time) {
        ll count = 0;
        for (int i = 0; i < n; i++) {
            count += (mid / time[i]);
            // 剪枝点:防止累加和超过 long long 范围
            // 同时也符合“只要大于等于就可行”的逻辑
            if (count >= totalTrips) return true;
        }
        return count >= totalTrips;
    }
    
    ll solve(int n, ll totalTrips, const vector<int>& time) {
        int min_t = time[0];
        for (int x : time) if (x < min_t) min_t = x;
    
        ll low = 1;
        // 右边界最坏情况:由最慢的一辆车跑完所有趟数
        // 此处设为 min_t * totalTrips 是为了更紧确,防止 mid 计算溢出
        ll high = (ll)min_t * totalTrips;
        ll ans = high;
    
        while (low <= high) {
            ll mid = low + (high - low) / 2;
            if (check(mid, totalTrips, n, time)) {
                ans = mid;
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return ans;
    }
    
    /**
     * 数据生成逻辑
     */
    void generate_test_cases() {
        mt19937_64 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 totalTrips;
            vector<int> time_arr;
    
            // 根据测试点分布设定规模
            if (t <= 2) { // 暴力解法可通过
                n = 100; totalTrips = 100;
            } else if (t <= 4) { // 模拟解法可通过
                n = 1000; totalTrips = 100000;
            } else if (t == 5) { // 边界:趟数极少
                n = 100000; totalTrips = 1;
            } else if (t == 6) { // 边界:车数极少
                n = 1; totalTrips = 10000000;
            } else { // 极限规模
                n = 100000; totalTrips = 10000000;
            }
    
            // 填充 time 数组
            if (t == 7) { // 均匀分布
                int fixed_t = rng() % 10000000 + 1;
                for (int i = 0; i < n; i++) time_arr.push_back(fixed_t);
            } else if (t == 8) { // 极限大值
                for (int i = 0; i < n; i++) time_arr.push_back(10000000);
            } else { // 随机分布
                for (int i = 0; i < n; i++) time_arr.push_back(rng() % 10000000 + 1);
            }
    
            // 1. 写入输入文件
            fout << n << " " << totalTrips << "\n";
            for (int i = 0; i < n; i++) {
                fout << time_arr[i] << (i == n - 1 ? "" : " ");
            }
            fout << endl;
    
            // 2. 运行标程并写入输出文件
            ll result = solve(n, totalTrips, time_arr);
            fans << result << endl;
    
            cout << "Generated Case " << t << " [N=" << n << ", T=" << totalTrips << "] OK." << endl;
    
            fout.close();
            fans.close();
        }
    }
    
    int main() {
        cout << "Starting data generation..." << endl;
        generate_test_cases();
        cout << "All 10 cases generated successfully." << endl;
        return 0;
    }
    

    三、 生成器关键点与优化说明

    1. 文件大小控制

      • 本题最大的开销在于第 7-10 组数据的 time 数组。
      • N=105N=10^5,每个数字最大 8 位(含空格),单个 .in 文件约为 105×80.8MB10^5 \times 8 \approx 0.8\text{MB}
      • 全部 10 组数据的总大小(压缩前)约为 5MB。如果你需要严格控制在 2MB 以内,建议将后几组的 NN 改为 3×1043 \times 10^4,这依然能有效卡掉 O(AnsN)O(Ans \cdot N) 的暴力,并对 O(TotalTripslogN)O(TotalTrips \log N) 的模拟造成压力。
    2. 防止溢出(Exception Control)

      • 计算溢出:在 check 函数中,我们使用了 if (count >= totalTrips) return true; 进行剪枝。这不仅能节约计算时间,更重要的是防止了 count 累加超过 long long 的最大值(9×10189 \times 10^{18}),因为在极端情况下(10510^5 辆车,每辆 mid/time=1014mid/time=10^{14}),不剪枝会导致数据错误。
      • 二分溢出high 动态设为 min_t * totalTrips。若设为固定的 1e18,虽然不会溢出 long long,但会让 check 函数内部计算更易发生越界。
    3. 区分算法复杂度

      • 暴力枚举时间:在测试点 1-4 之后会因为 TotalTripsTotalTrips 达到 10710^7 而彻底失效。
      • 堆/优先队列模拟:在 TotalTrips=107,N=105TotalTrips=10^7, N=10^5 的情况下,模拟需要 10710^7 次堆操作,耗时约 2-3 秒以上,在 NOI 严格的 1 秒限时下通常无法全部 AC。
    4. 非递归写法

      • 标程中的二分查找采用 while 循环迭代实现,确保不会因为递归深度过大(虽然 log\log 的深度本就不深)导致系统栈溢出。

    四、 如何使用

    1. 编译运行此 generator.cpp
    2. 将生成的 1.in ~ 10.out 存入测试数据目录。
    3. 设置题目时限为 1.0s,内存限制为 256MB

    信息

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