2 条题解

  • 0
    @ 2025-12-17 17:08:30

    你好!这是为你准备的完整数据生成器。这份代码集成了标准AC解法10种针对性测试数据生成策略,能够自动生成符合OJ要求的 1.in/1.out10.in/10.out 文件。

    生成器特点

    1. 全自动流程:一键编译运行,自动生成所有输入输出文件。
    2. 覆盖全面:包含极小值边界、全重复、全不重复、负数、最大数据量、高频重复等多种场景。
    3. 标准格式:生成的输入输出完全符合NOIP竞赛及题目描述的格式要求。
    4. 高效稳健:使用 O(N)O(N) 的核心算法生成答案,数据生成部分使用 O(NlogN)O(N \log N) 排序,对于 N=30000N=30000 的数据规模瞬间完成。

    C++ 数据生成器代码

    请保存为 generator.cpp,编译并运行。

    /**
     * 题目:删除有序数组中的重复项 II - 数据生成器
     * 功能:生成 1.in/1.out ~ 10.in/10.out
     * 环境:C++14 Standard
     * 作者:OI Gold Medal Coach
     */
    
    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <algorithm>
    #include <random>
    #include <ctime>
    #include <string>
    
    using namespace std;
    
    // ==========================================
    // Part 1: 标准 AC 解法 (Solver)
    // ==========================================
    // 用于生成 .out 标准答案
    class Solver {
    public:
        // 返回处理后的数组
        vector<int> solve(int n, vector<int> nums) {
            if (n <= 2) return nums;
    
            int slow = 2;
            for (int fast = 2; fast < n; ++fast) {
                // 核心逻辑:允许保留的条件是与 slow-2 位置不同
                if (nums[fast] != nums[slow - 2]) {
                    nums[slow] = nums[fast];
                    slow++;
                }
            }
            
            // 截断数组,只保留有效部分
            nums.resize(slow);
            return nums;
        }
    };
    
    // ==========================================
    // Part 2: 数据生成工具箱
    // ==========================================
    mt19937 rng(time(0));
    
    // 生成指定长度、指定数值范围的随机有序数组
    vector<int> gen_random_sorted(int n, int min_val, int max_val) {
        vector<int> res(n);
        uniform_int_distribution<int> dist(min_val, max_val);
        for(int i = 0; i < n; ++i) {
            res[i] = dist(rng);
        }
        // 题目要求输入必须是有序的
        sort(res.begin(), res.end());
        return res;
    }
    
    // 生成全重复数组
    vector<int> gen_all_same(int n, int val) {
        return vector<int>(n, val);
    }
    
    // 生成全不重复数组 (连续序列)
    vector<int> gen_unique_seq(int n, int start_val) {
        vector<int> res(n);
        for(int i = 0; i < n; ++i) res[i] = start_val + i;
        return res;
    }
    
    // 生成特定模式:每个数重复 k 次 (e.g., 1,1,1, 2,2,2...)
    vector<int> gen_pattern_k_reps(int n, int k) {
        vector<int> res;
        int val = -1000; // 起始值
        while (res.size() < n) {
            for (int j = 0; j < k && res.size() < n; ++j) {
                res.push_back(val);
            }
            val++;
        }
        return res;
    }
    
    // 生成高密度重复数据 (只有很少的 unique values)
    vector<int> gen_dense_reps(int n, int unique_count) {
        vector<int> res(n);
        uniform_int_distribution<int> dist(0, unique_count - 1);
        for(int i=0; i<n; ++i) {
            res[i] = dist(rng); // 数值范围很小,必然大量重复
        }
        sort(res.begin(), res.end());
        return res;
    }
    
    // ==========================================
    // Part 3: 主程序
    // ==========================================
    int main() {
        Solver solver;
        cout << "Generating data..." << endl;
    
        for (int i = 1; i <= 10; ++i) {
            string in_file_name = to_string(i) + ".in";
            string out_file_name = to_string(i) + ".out";
            
            ofstream fin(in_file_name);
            ofstream fout(out_file_name);
            
            int N;
            vector<int> input_nums;
            string desc;
    
            // ---- 针对不同测试点设计数据 ----
            switch(i) {
                case 1: 
                    // 极小边界:N=1
                    N = 1;
                    input_nums = {10};
                    desc = "Min N=1";
                    break;
                case 2: 
                    // 极小边界:N=2 (无需删除)
                    N = 2;
                    input_nums = {10, 10};
                    desc = "Min N=2";
                    break;
                case 3: 
                    // 极小边界:N=3 (需要删除)
                    N = 3;
                    input_nums = {5, 5, 5};
                    desc = "Min N=3 All Same";
                    break;
                case 4: 
                    // 特例:全不重复
                    N = 100;
                    input_nums = gen_unique_seq(N, -50);
                    desc = "All Unique";
                    break;
                case 5: 
                    // 特例:全重复 (由于 N 较大,删除量大)
                    N = 100;
                    input_nums = gen_all_same(N, 7);
                    desc = "All Same Large";
                    break;
                case 6: 
                    // 特例:每个数重复 3 次 (测试边界条件 >2)
                    N = 1000;
                    input_nums = gen_pattern_k_reps(N, 3);
                    desc = "Pattern: 3 Reps each";
                    break;
                case 7: 
                    // 正常随机:含负数
                    N = 2000;
                    input_nums = gen_random_sorted(N, -2000, 2000);
                    desc = "Random with Negatives";
                    break;
                case 8: 
                    // 大数据:最大 N,稀疏重复 (值域大)
                    N = 30000;
                    input_nums = gen_random_sorted(N, -10000, 10000);
                    desc = "Max N, Sparse";
                    break;
                case 9: 
                    // 大数据:最大 N,高频重复 (值域极小)
                    // 只有 10 种不同的数,分布在 30000 个位置上
                    N = 30000;
                    input_nums = gen_dense_reps(N, 10);
                    desc = "Max N, Very Dense";
                    break;
                case 10:
                    // 大数据:随机混合
                    N = 30000;
                    input_nums = gen_random_sorted(N, -5000, 5000);
                    desc = "Max N, General Random";
                    break;
            }
    
            // ---- 写入 .in 文件 ----
            // 格式:
            // N
            // num1 num2 ... numN
            fin << N << endl;
            for (int k = 0; k < N; ++k) {
                fin << input_nums[k] << (k == N - 1 ? "" : " ");
            }
            fin << endl; // 保持文件末尾换行习惯
    
            // ---- 计算并写入 .out 文件 ----
            vector<int> result = solver.solve(N, input_nums);
            
            // 格式:
            // len
            // res1 res2 ... resLen
            fout << result.size() << endl;
            for (size_t k = 0; k < result.size(); ++k) {
                fout << result[k] << (k == result.size() - 1 ? "" : " ");
            }
            fout << endl;
    
            // ---- 关闭文件 ----
            fin.close();
            fout.close();
    
            cout << "Generated Case " << i << ": " << desc << " [N=" << N << "]" << endl;
        }
    
        cout << "\nAll 10 test cases generated successfully!" << endl;
        return 0;
    }
    

    使用说明

    1. 运行环境:任何支持 C++11 及以上的编译器(g++, clang++, Visual Studio 等)。
    2. 生成结果:运行后当前目录下会出现 1.in ~ 10.in1.out ~ 10.out
    3. OJ配置建议
      • 时间限制:1000ms (C++ 对于 N=30000N=30000 的处理是微秒级的,时间极度宽裕)。
      • 内存限制:128MB。
      • 判题模式:这是 Special Judge 类型的题目(因为通常 OJ 比较的是文本),但由于题目要求有序稳定保留前两个,所以结果是唯一确定的。你可以直接使用标准的文本比对(Ignore trailing spaces/newlines)进行判题,不需要写额外的 SPJ。

    测试点覆盖详情

    • 测试点 1-3: 考察 N=1,2,3N=1, 2, 3 的极小值处理,防止数组越界。
    • 测试点 4: 全不重复,验证算法是否会错误删除。
    • 测试点 5: 全重复,验证算法是否能正确压缩到只剩2个。
    • 测试点 6: 固定重复3次,精准打击边界判断逻辑(slow-2 是否生效)。
    • 测试点 7: 包含负数,考察数据类型处理。
    • 测试点 8: N=30000N=30000 且值域分散,模拟一般情况。
    • 测试点 9: N=30000N=30000 且值域极小(只有10种数),模拟极端稠密情况,考察频繁写入。
    • 测试点 10: N=30000N=30000 综合随机,压测性能。
    • 0
      @ 2025-12-17 17:06:31

      你好!我是你的OI竞赛金牌教练。

      基于上一条我们改写的题目描述,下面是完全符合 NOIP/CSP 竞赛标准的 C++14 题解代码。代码中包含了详细的注释,解释了双指针的运作机制和易错点。


      NOIP 标准题解代码 (C++14)

      /**
       * 题目:删除有序数组中的重复项 II
       * 算法:双指针 (快慢指针) / 原地算法
       * 时间复杂度:O(N)
       * 空间复杂度:O(1)
       * 风格:NOIP/CSP 竞赛标准
       */
      
      #include <iostream>
      #include <vector>
      #include <algorithm>
      
      using namespace std;
      
      // 为了应对可能的 IO 瓶颈,在 main 开头进行加速
      void fast_io() {
          ios::sync_with_stdio(false);
          cin.tie(nullptr);
      }
      
      int main() {
          fast_io();
      
          // 1. 读入数据
          int n;
          if (!(cin >> n)) return 0; // 鲁棒性检查
      
          // 使用 vector 存储,方便动态管理,但在逻辑上我们视为定长数组处理
          vector<int> nums(n);
          for (int i = 0; i < n; ++i) {
              cin >> nums[i];
          }
      
          // 2. 算法核心逻辑:双指针法
          // 题目允许每个元素最多出现 2 次。
          // 如果数组长度 <= 2,无论如何都不需要删除,直接输出原数组即可。
          if (n <= 2) {
              cout << n << endl;
              for (int i = 0; i < n; ++i) {
                  cout << nums[i] << (i == n - 1 ? "" : " ");
              }
              cout << endl;
              return 0;
          }
      
          // 定义慢指针 slow:
          // slow 指向的是“当前处理好的有效数组”的【下一个写入位置】。
          // 因为前两个元素 (下标0, 1) 必然保留(最多允许2个重复),所以 slow 从 2 开始。
          int slow = 2;
      
          // 定义快指针 fast:
          // fast 用于遍历数组,寻找有资格放入有效数组的新元素。
          // fast 也从 2 开始遍历。
          for (int fast = 2; fast < n; ++fast) {
              // 【关键逻辑 / 易错点】
              // 我们要检查 nums[fast] 是否可以写入 nums[slow] 的位置。
              // 允许保留的条件是:当前考察的元素 nums[fast] 不等于 有效区倒数第二个元素 nums[slow-2]。
              // 
              // 为什么是 slow-2?
              // 因为数组是有序的。如果 nums[fast] == nums[slow-2],
              // 那么意味着 nums[slow-2] == nums[slow-1] == nums[fast]。
              // 这时 nums[fast] 就是该数值的第 3 个副本,属于多余元素,需要丢弃。
              // 反之,如果不相等,说明 nums[fast] 是该数值的第 1 个或第 2 个副本,可以保留。
              if (nums[fast] != nums[slow - 2]) {
                  nums[slow] = nums[fast]; // 将符合条件的元素“搬运”过来
                  slow++;                  // 有效区长度 +1,指针后移
              }
          }
      
          // 3. 输出结果
          // 此时 slow 的值即为处理后的新长度
          cout << slow << endl;
          for (int i = 0; i < slow; ++i) {
              // 题目要求输出前 len 个元素,以空格分隔
              cout << nums[i] << (i == slow - 1 ? "" : " ");
          }
          cout << endl;
      
          return 0;
      }
      

      复杂度分析与思考过程

      1. 时间复杂度分析

      • 思考过程
        • 我们使用了一个 for 循环,快指针 fast 从下标 2 遍历到 N1N-1
        • 在循环内部,只进行了简单的比较 if 和赋值操作 nums[slow] = nums[fast],这些都是 O(1)O(1) 的常数时间操作。
        • 每个元素最多被访问和搬运一次。
      • 结论:总时间复杂度为 O(N)O(N),其中 NN 是数组的长度。
      • 优化建议:对于本题,O(N)O(N) 已经是理论下界(必须遍历一次数组才能知道哪些要删),无法在算法层级进一步降低量级。唯一的优化是 IO 优化(如代码中的 ios::sync_with_stdio(false)),防止输入输出数据量巨大时 TLE。

      2. 空间复杂度分析

      • 思考过程
        • 题目明确要求 原地 (In-place) 修改,空间复杂度限制为 O(1)O(1)
        • 我们定义了两个整数变量 slowfast,以及少量的辅助变量。
        • 我们直接在原数组 nums 上进行覆盖操作,没有开辟新的数组(比如 vector<int> result)。
        • 注意:输入存储本身的 O(N)O(N) 空间通常不计入算法的额外空间复杂度。
      • 结论:额外空间复杂度为 O(1)O(1)

      核心逻辑图解 (草稿纸推演)

      假设输入:1 1 1 2 2 3

      1. 初始状态

        • slow = 2 (指向第3个位置)
        • 前两个 1 1 默认保留。
        • 数组现状:[1, 1, 1, 2, 2, 3]
      2. Fast = 2, Value = 1:

        • 比较:nums[fast] (1) vs nums[slow-2] (即nums[0]=1)
        • 1 == 1,相等!说明这是第3个1。跳过
        • slow 保持 2。
      3. Fast = 3, Value = 2:

        • 比较:nums[fast] (2) vs nums[slow-2] (即nums[0]=1)
        • 2 != 1,不等!合法。
        • 搬运nums[slow] = 2。数组变为 [1, 1, 2, 2, 2, 3]
        • slow 变为 3。
      4. Fast = 4, Value = 2:

        • 比较:nums[fast] (2) vs nums[slow-2] (即nums[1]=1)
        • 2 != 1,不等!合法。
        • 搬运nums[slow] = 2。数组变为 [1, 1, 2, 2, 2, 3] (看似没变,其实覆盖了)。
        • slow 变为 4。
      5. Fast = 5, Value = 3:

        • 比较:nums[fast] (3) vs nums[slow-2] (即nums[2]=2)
        • 3 != 2,不等!合法。
        • 搬运nums[slow] = 3。数组变为 [1, 1, 2, 2, 3, 3]
        • slow 变为 5。
      6. 结束

        • 返回 slow = 5
        • 输出前5个:1 1 2 2 3

      总结关键点 (Coach's Note)

      1. 通用扩展性:这道题的逻辑其实是通用的。如果题目改成“允许最多出现 KK 次”,你只需要把代码里的 slow - 2 改成 slow - K,把初始的 slow = 2 改成 slow = K 即可。这是面试和竞赛中常考的变种。
      2. 覆盖而非交换:注意我们做的是 nums[slow] = nums[fast](覆盖),而不是 swap。因为被覆盖掉的那个旧垃圾值我们并不需要保留。
      3. 边界保护:代码中 if (n <= 2) 的判断虽然删掉也能跑通(如果逻辑写得足够好),但在竞赛中显式处理边界情况能避免很多越界错误,也是良好的编程习惯。
      • 1

      信息

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