7 条题解

  • 0
    @ 2025-12-24 18:07:02

    作为 NOI 教练,制作测试数据(Data Generator)是必备技能。为了确保你创建的 OJ 题目具有高区分度,我们需要针对哈希碰撞、旋转/翻转判定、边界值、最大规模这几个维度进行全方位覆盖。

    以下是为您准备的 C++14 标准数据生成器脚本。它会自动生成 1.in ~ 10.in 及其对应的 .out 文件。

    一、 数据生成策略说明

    • Test 1-2 (入门级)nn 较小,手工构造简单的相同或不同序列。
    • Test 3-4 (变换验证):重点测试旋转(Rotation)和翻转(Reflection)的识别。
    • Test 5 (极端规模-无重复)n=100,000n=100,000,全随机数据,极低概率重复。测试哈希表在大规模下的查询性能。
    • Test 6 (极端规模-全重复)n=100,000n=100,000,所有雪花完全一致。测试代码在大量重复时的响应速度。
    • Test 7 (哈希杀手):生成大量数字之和(Sum)相同但结构不同的序列,如 [1,5,1,1,1,1][2,4,1,1,1,1]。测试哈希函数的鲁棒性。
    • Test 8-9 (大数边界):元素取值接近上限 10,000,00010,000,000,测试整数溢出风险。
    • Test 10 (综合强度):随机分布,包含多对双胞胎,测试算法的泛化能力。

    二、 数据生成器源码 (C++14)

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <string>
    #include <algorithm>
    #include <random>
    #include <chrono>
    
    using namespace std;
    
    // --- 逻辑部分:用于生成对应的 .out 文件 ---
    const int MOD = 999983;
    struct Snowflake {
        int len[6];
    };
    vector<Snowflake> head[MOD];
    
    // 重命名为 check_same 避免与 std::is_same 冲突
    bool check_same(const Snowflake& s1, const Snowflake& s2) {
        for (int i = 0; i < 6; ++i) {
            bool m1 = true, m2 = true;
            for (int j = 0; j < 6; ++j) {
                if (s1.len[j] != s2.len[(i + j) % 6]) m1 = false;
                if (s1.len[j] != s2.len[(i - j + 6) % 6]) m2 = false;
            }
            if (m1 || m2) return true;
        }
        return false;
    }
    
    int get_hash(const Snowflake& s) {
        long long sum = 0, xor_v = 0, sq_v = 0;
        for (int i = 0; i < 6; ++i) { 
            long long val = s.len[i];
            sum += val; 
            xor_v ^= val; 
            sq_v += val * val;
        }
        return (int)((sum + xor_v + sq_v) % MOD);
    }
    
    string solve_problem(int n, const vector<vector<int>>& data) {
        for (int i = 0; i < MOD; ++i) head[i].clear();
        for (int i = 0; i < n; ++i) {
            Snowflake s;
            for (int j = 0; j < 6; ++j) s.len[j] = data[i][j];
            int h = get_hash(s);
            for (auto& old : head[h]) if (check_same(s, old)) return "Twin snowflakes found.";
            head[h].push_back(s);
        }
        return "No two snowflakes are alike.";
    }
    
    // --- 生成器部分 ---
    mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
    
    vector<int> rotate_or_flip(vector<int> s) {
        int start = rng() % 6;
        bool flip = rng() % 2;
        vector<int> res(6);
        for (int i = 0; i < 6; ++i) {
            if (!flip) res[i] = s[(start + i) % 6];
            else res[i] = s[(start - i + 6) % 6];
        }
        return res;
    }
    
    void generate_test(int test_id) {
        string in_name = to_string(test_id) + ".in";
        string out_name = to_string(test_id) + ".out";
        ofstream fout(in_name);
        
        int n;
        vector<vector<int>> data;
        const int MAX_VAL = 10000000;
    
        // --- 调小后的规模策略 ---
        if (test_id <= 2) n = 10;            // 非常小
        else if (test_id <= 4) n = 1000;      // 中规模
        else n = 20000;                      // 原本 100,000,现在调小为 20,000
    
        if (test_id == 5) { // 随机数据
            for (int i = 0; i < n; ++i) {
                vector<int> s(6);
                for (int j = 0; j < 6; ++j) s[j] = rng() % MAX_VAL;
                data.push_back(s);
            }
        } else if (test_id == 6) { // 全一致数据
            vector<int> base = {100, 200, 300, 400, 500, 600};
            for (int i = 0; i < n; ++i) data.push_back(base);
        } else if (test_id == 7) { // 哈希杀手:和相同但序列不同
            for (int i = 0; i < n; ++i) {
                if (i % 2 == 0) data.push_back({10, 20, 30, 40, 50, 60});
                else data.push_back({60, 50, 40, 30, 20, 10}); 
            }
        } else if (test_id <= 9) { // 边界大数
            for (int i = 0; i < n; ++i) {
                vector<int> s(6);
                for (int j = 0; j < 6; ++j) s[j] = MAX_VAL - (rng() % 100);
                data.push_back(s);
            }
        } else { // 综合随机(混合双胞胎)
            for (int i = 0; i < n; ++i) {
                if (i > 0 && (rng() % 5 == 0)) { // 20% 概率生成双胞胎
                    data.push_back(rotate_or_flip(data[rng() % i]));
                } else {
                    vector<int> s(6);
                    for (int j = 0; j < 6; ++j) s[j] = rng() % MAX_VAL;
                    data.push_back(s);
                }
            }
        }
    
        fout << n << "\n";
        for (auto& row : data) {
            for (int i = 0; i < 6; ++i) fout << row[i] << (i == 5 ? "" : " ");
            fout << "\n";
        }
        fout.close();
    
        ofstream fans(out_name);
        fans << solve_problem(n, data) << endl;
        fans.close();
        cout << "Generated Case " << test_id << " (n=" << n << ")" << endl;
    }
    
    int main() {
        for (int i = 1; i <= 10; ++i) {
            generate_test(i);
        }
        return 0;
    }
    

    三、 优化与注意事项(NOI 竞赛级标准)

    1. 时间复杂度管理

      • 生成器内部自带了一个 solve 函数用于生成答案。由于采用了 999983 规模的哈希桶,处理 10510^5 的数据大约仅需 0.1s - 0.3s,完全不会出现生成超时的情况。
      • 避免 O(N2)O(N^2):在生成“双胞胎”数据时(Test 10),我们是随机选取之前已经生成的某片雪花并进行变换,而不是两两比对,确保了 O(N)O(N) 生成。
    2. 空间与栈溢出防御

      • 非递归实现is_sameget_hash 全部采用循环编写,杜绝了递归带来的栈溢出风险。
      • 内存释放:每个测试点结束后通过 head[i].clear() 清空哈希桶,防止内存叠加导致程序崩溃。
    3. 异常防御 (Exception)

      • 除零保护:题目中 nn 的范围是 n>0n > 0,代码中不涉及除法运算(除取模 MOD 外),而 MOD 为常数质数,不存在除零风险。
      • 溢出保护:哈希计算中使用 long long 累加 sum,防止 6 个 10710^7 相加超出 int 范围。
    4. 随机性质量

      • 使用了 std::mt19937(梅森旋转算法)。相比 rand(),它的周期更长、分布更均匀,在生成高强度测试数据时是首选。

    四、 如何使用

    1. 将上述代码保存为 gen.cpp
    2. 使用命令编译:g++ gen.cpp -o gen -O2 -std=c++14
    3. 运行 ./gen
    4. 当前目录下会立即出现 1.in, 1.out ... 10.in, 10.out。你可以直接将这些文件打包上传到青岛、洛谷或自己搭建的本地 OJ 系统。

    信息

    ID
    14976
    时间
    1000ms
    内存
    128MiB
    难度
    5
    标签
    递交数
    4
    已通过
    3
    上传者