2 条题解

  • 0
    @ 2025-12-24 17:35:28

    作为金牌教练,制作数据不仅要追求随机性,更要针对算法的边界条件进行“定点爆破”。

    本题的核心在于哈希表的效率。我们需要构造能够覆盖“极长序列”、“极碎序列”、“大规模重复”以及“大数值差”的测试点。由于 n=105n=10^5 且涉及哈希,生成器必须保证在 O(n)O(n)O(nlogn)O(n \log n) 时间内完成计算,否则生成 1010 组数据会非常缓慢。

    以下是为你准备的自动化数据生成器。它集成了 O(n)O(n) 标程逻辑,采用非递归写法,确保生成的每一组数据都有准确的 .out 结果。

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

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <string>
    #include <unordered_set>
    #include <random>
    #include <ctime>
    #include <algorithm>
    
    using namespace std;
    
    // ================= 标准标程逻辑 (用于生成 .out) =================
    // 复杂度 O(n),用于计算标准答案
    int solve_standard(int n, const vector<int>& nums) {
        if (n == 0) return 0;
        unordered_set<int> st;
        for (int x : nums) st.insert(x);
    
        int longest_streak = 0;
        for (int num : st) {
            // 只有当 num 是序列起点时才开始计数
            if (st.find(num - 1) == st.end()) {
                int current_num = num;
                int current_streak = 1;
                while (st.find(current_num + 1) != st.end()) {
                    current_num += 1;
                    current_streak += 1;
                }
                longest_streak = max(longest_streak, current_streak);
            }
        }
        return longest_streak;
    }
    
    // ================= 数据生成辅助函数 =================
    void write_test_case(int id, int n, vector<int>& nums) {
        string in_name = to_string(id) + ".in";
        string out_name = to_string(id) + ".out";
    
        // 1. 写入 .in 文件
        ofstream fout(in_name);
        fout << n << "\n";
        for (int i = 0; i < n; ++i) {
            fout << nums[i] << (i == n - 1 ? "" : " ");
        }
        fout << endl;
        fout.close();
    
        // 2. 计算并写入 .out 文件
        int ans = solve_standard(n, nums);
        ofstream fans(out_name);
        fans << ans << endl;
        fans.close();
    
        cout << "Testcase " << id << " generated: n=" << n << ", ans=" << ans << endl;
    }
    
    // ================= 主生成逻辑 =================
    int main() {
        // 使用 mt19937 提供高质量随机数
        mt19937 rng(time(0));
        uniform_int_distribution<int> dist_large(-1000000000, 1000000000);
    
        for (int i = 1; i <= 10; ++i) {
            int n;
            vector<int> nums;
    
            if (i == 1) { // 样例 1
                n = 6; nums = {100, 4, 200, 1, 3, 2};
            } 
            else if (i == 2) { // 边界:n=0 和 n=1
                n = 1; nums = {0};
            }
            else if (i == 3) { // 边界:全同元素
                n = 1000; nums.assign(n, 777);
            }
            else if (i == 4) { // 正序连续序列 (测试最简单 O(n))
                n = 5000;
                for (int j = 1; j <= n; ++j) nums.push_back(j);
            }
            else if (i == 5) { // 倒序连续序列
                n = 5000;
                for (int j = n; j >= 1; --j) nums.push_back(j);
            }
            else if (i == 6) { // 多个碎片化序列 (测试起点判定)
                n = 10000;
                for (int j = 0; j < n; ++j) {
                    nums.push_back((j / 10) * 100 + (j % 10)); 
                }
                // 生成如 0-9, 100-109, 200-209...
            }
            else if (i == 7) { // 极大值的跨度 (测试哈希表性能)
                n = 20000;
                for (int j = 0; j < n / 2; ++j) {
                    nums.push_back(j); // 序列 1
                    nums.push_back(j + 500000000); // 序列 2
                }
            }
            else if (i == 8) { // 极限负值与正值
                n = 50000;
                for (int j = 0; j < n; ++j) {
                    if (j % 2 == 0) nums.push_back(-1000000000 + j);
                    else nums.push_back(1000000000 - j);
                }
            }
            else if (i == 9) { // 最大规模 n=10^5, 一个超长序列 + 干扰项
                n = 100000;
                int start_val = dist_large(rng) / 2;
                for (int j = 0; j < 80000; ++j) nums.push_back(start_val + j);
                for (int j = 0; j < 20000; ++j) nums.push_back(dist_large(rng));
                shuffle(nums.begin(), nums.end(), rng);
            }
            else { // 最大规模 n=10^5, 稀疏随机分布
                n = 100000;
                for (int j = 0; j < n; ++j) nums.push_back(dist_large(rng));
            }
    
            write_test_case(i, n, nums);
        }
        return 0;
    }
    

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

    作为教练,我设计的这 10 个测试点覆盖了选手可能出错的每一个角落:

    测试点 数据特征 考察重点
    1 官方样例 确保基本逻辑正确。
    2-3 n1n \le 1 或重复值 考察对边界和去重的处理。
    4-5 连续递增/递减 考察 O(n)O(n) 算法在最理想/最简单情况下的表现。
    6 碎片化序列 核心考查点:考察是否正确过滤了非起点数字。如果逻辑不对(对每个数都往后搜),会退化到 O(n2)O(n^2)
    7-8 大数值/大跨度 考察哈希表处理极大整数的稳健性(避免数组桶排序的“偷鸡”做法)。
    9 80% 连续 + 20% 随机 压力测试n=105n=10^5。测试 while 循环在长序列下的效率。
    10 极限稀疏随机 压力测试n=105n=10^5。测试 unordered_set 的查找效率和抗哈希冲突能力。

    三、 考场避坑建议 (针对本题)

    1. 关于 std::unordered_set 的稳定性: 在 NOI 竞赛中,如果 nn 很大且时间限制紧(如 0.5s),要注意哈希表的常数。如果发现程序变慢,检查是否是因为频繁的 Rehash

      • 技巧:在生成器和选手的代码中,建议使用 num_set.reserve(n)。这可以大幅减少动态分配内存的时间。
    2. 避免使用 std::set: 如果选手使用了 std::set(红黑树,O(nlogn)O(n \log n)),在测试点 9 和 10 可能会因为对 10510^5 个元素进行 logn\log n 次查找而超时。

    3. 数据溢出: 虽然 numsinums_iint 范围内,但 num + 1num 等于 23112^{31}-1 时会发生溢出。

      • 对策:在本题的逻辑中,建议使用 long long current_num = num; 来进行 current_num + 1 的运算。
    4. 非递归安全: 本生成器和标程均不涉及递归,所有序列查询均通过 while 迭代完成。即使序列长度达到 10510^5,也不会触发系统栈溢出。

    你可以直接运行此生成器,它会自动在当前目录下生成一套高质量的练习数据。加油!

    信息

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