2 条题解

  • 0
    @ 2025-12-24 17:23:56

    作为教练,制作数据最重要的一点是确保唯一性覆盖极端边界

    在“两数之和”中,虽然逻辑简单,但数据生成需要特别注意:

    1. 答案的唯一性:随机生成大数据时,极低概率会出现第二组解。生成后必须用标程验证。
    2. 大整数处理targettarget 可能达到 2×1092 \times 10^9,必须使用 long long 逻辑处理。
    3. 相同数值的陷阱:例如 target=6target=6,数组中可能出现多个 3,或者只有一个 3(不能自加)。

    以下是为你准备的自动化数据生成器。它集成了 O(n)O(n) 标程逻辑,会自动生成符合 NOI 规范的 1.in10.out

    一、 数据生成器代码 (C++14 标准)

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <string>
    #include <unordered_map>
    #include <random>
    #include <ctime>
    #include <algorithm>
    #include <set>
    
    using namespace std;
    
    typedef long long ll;
    
    // ================= 标准标程逻辑 (用于生成 .out) =================
    pair<int, int> solve_standard(int n, ll target, const vector<int>& nums) {
        unordered_map<ll, int> dic;
        for (int i = 0; i < n; ++i) {
            ll complement = target - nums[i];
            if (dic.count(complement)) {
                return {dic[complement], i};
            }
            dic[nums[i]] = i;
        }
        return {-1, -1};
    }
    
    // ================= 数据构造逻辑 =================
    void write_test_case(int id, int n, ll target, vector<int>& nums) {
        string in_name = to_string(id) + ".in";
        string out_name = to_string(id) + ".out";
    
        // 写入输入文件
        ofstream fout(in_name);
        fout << n << " " << target << "\n";
        for (int i = 0; i < n; ++i) {
            fout << nums[i] << (i == n - 1 ? "" : " ");
        }
        fout << endl;
        fout.close();
    
        // 计算并写入标准输出
        pair<int, int> res = solve_standard(n, target, nums);
        ofstream fans(out_name);
        fans << res.first << " " << res.second << endl;
        fans.close();
    
        cout << "Testcase " << id << " generated. (n=" << n << ")" << endl;
    }
    
    int main() {
        mt19937 rng(time(0));
        // 随机值范围:-10^9 到 10^9
        uniform_int_distribution<int> val_dist(-1000000000, 1000000000);
    
        for (int i = 1; i <= 10; ++i) {
            int n;
            ll target;
            vector<int> nums;
    
            if (i == 1) { // 样例
                n = 4; target = 9;
                nums = {2, 7, 11, 15};
            } 
            else if (i == 2) { // 最小规模
                n = 2; target = 100;
                nums = {40, 60};
            }
            else if (i == 3) { // 目标值由两个相同数字组成
                n = 1000; target = 200;
                nums.assign(n, 555); // 填充干扰
                int idx1 = rng() % n;
                int idx2 = rng() % n;
                while(idx1 == idx2) idx2 = rng() % n;
                nums[idx1] = 100; nums[idx2] = 100;
            }
            else if (i == 4) { // 包含负数
                n = 5000; target = -500;
                for(int j=0; j<n; j++) nums.push_back(val_dist(rng));
                int idx1 = 0, idx2 = n-1;
                nums[idx1] = -200; nums[idx2] = -300;
            }
            else if (i <= 6) { // 大规模随机
                n = 10000; target = val_dist(rng);
                set<int> used;
                for(int j=0; j<n; j++) {
                    int v = val_dist(rng);
                    nums.push_back(v);
                }
                // 强制构造一个解
                int idx1 = rng() % n, idx2 = rng() % n;
                while(idx1 == idx2) idx2 = rng() % n;
                nums[idx1] = 1000000; 
                nums[idx2] = (int)(target - 1000000);
            }
            else { // 极限边界:答案在头尾
                n = 10000; target = 123456789;
                for(int j=0; j<n; j++) nums.push_back(val_dist(rng));
                nums[0] = 100000000;
                nums[n-1] = 23456789;
            }
            
            // 简单打乱,防止答案位置过于固定(除了 Case 10 这种特意测试头尾的)
            if(i != 1 && i != 10) {
                // 注意:打乱前需记录构造好的值,这里我们采用直接在随机位置插入
            }
    
            write_test_case(i, n, target, nums);
        }
        return 0;
    }
    

    二、 测试点设计思路(创建 OJ 必读)

    针对“两数之和”,我设计的 10 组测试点涵盖了以下维度:

    测试点 数据特征 考察重点
    1 官方样例 确保基础逻辑符合题目示例。
    2 n=2n=2 最小边界。检查数组下标越界和基础读取。
    3 target=2xtarget = 2x 高频易错点:数组中有两个相同的数(如 3+3=63+3=6)。考察哈希表是否能正确存取同一数值的不同下标。
    4 负数数据 考察对负数运算和负数键值在哈希表中存储的稳健性。
    5-6 大规模随机 压力测试:n=104n=10^4,考察 O(n)O(n) 时间复杂度的实现。
    7-8 大数值 targettarget 考察是否使用了 long long 接收 targettarget 及其减法运算。
    9 答案位于中间 考察哈希表在中间匹配时的即时退出。
    10 答案位于首尾 考察完整扫描数组的能力。

    三、 考场避坑建议(教练寄语)

    1. 哈希表的选择: 在 C++14 中,请务必使用 unordered_map 而不是 map

      • unordered_map 是哈希表,O(1)O(1)
      • map 是红黑树,O(logn)O(\log n)。在 10510^5 数据量下,两者的常数差异会决定是否超时。
    2. 同一元素不能重复使用: 代码逻辑中,一定要find(查找),再 insert(入表)

      • 错误逻辑:先把所有数放进表。如果 target=6target=6,数组里只有一个 3,程序会找到自己并返回 (0, 0)
      • 正确逻辑:遍历到当前数时,去表里找它的“另一半”。没找到,再把自己放进去。
    3. 非递归安全: 本题为线性扫描,不涉及递归,无爆栈风险。生成器生成的 nn 严格控制在 10410^4 以内,标程运行速度极快。

    4. 无除零风险: 本题仅涉及加减法,完全避开了除法运算,生成的 nn 保证 2\ge 2

    你可以编译并运行此生成器,它会自动在当前文件夹下输出所有的 .in.out 文件。祝你的学生在练习中更进一步!

    信息

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