2 条题解

  • 0
    @ 2025-12-17 18:04:22

    这是为你准备的完整数据生成器。这份代码集成了绝对正确的解题逻辑(用于生成标准输出)和10种针对性测试数据生成策略

    它会直接在当前目录下生成 1.in/1.out10.in/10.out,能够完美覆盖题目要求的边界情况(如某数组为空)和正常情况(如乱序后的归并、重复元素、负数等)。

    C++ 数据生成器代码

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

    /**
     * 题目:合并两个有序数组 (Merge Sorted Array) - 数据生成器
     * 功能:生成 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: 标准解法 (Oracle / Solver)
    // ==========================================
    // 在数据生成器中,为了保证生成的 .out 文件绝对正确(Ground Truth),
    // 我们通常使用最简单粗暴但绝对可靠的方法,即 std::sort。
    // 虽然题目要求学生写 O(m+n) 的解法,但判题机的标准答案只需要结果正确。
    class Solver {
    public:
        vector<int> solve(vector<int> nums1, vector<int> nums2) {
            vector<int> merged = nums1;
            // 将 nums2 追加到 nums1 后面
            merged.insert(merged.end(), nums2.begin(), nums2.end());
            // 直接排序,获取标准答案
            sort(merged.begin(), merged.end());
            return merged;
        }
    };
    
    // ==========================================
    // Part 2: 数据生成工具箱
    // ==========================================
    mt19937 rng(time(0));
    
    // 生成指定长度、指定范围的随机有序数组
    vector<int> gen_random_sorted(int len, int min_val, int max_val) {
        vector<int> res(len);
        if (len == 0) return res;
        
        uniform_int_distribution<int> dist(min_val, max_val);
        for(int i = 0; i < len; ++i) {
            res[i] = dist(rng);
        }
        // 题目输入要求数组是有序的
        sort(res.begin(), res.end());
        return res;
    }
    
    // ==========================================
    // Part 3: 主程序
    // ==========================================
    int main() {
        Solver solver;
        cout << "Generating test data for Merge Sorted Array..." << 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 m, n;
            vector<int> nums1, nums2;
            string desc;
    
            // ---- 针对不同测试点设计数据 ----
            switch(i) {
                case 1: 
                    // 边界:极小值 m=1, n=1
                    m = 1; n = 1;
                    nums1 = {10};
                    nums2 = {5};
                    desc = "Smallest Non-empty (m=1, n=1)";
                    break;
                case 2: 
                    // 边界:nums1 为空 (m=0)
                    m = 0; n = 5;
                    nums1 = {};
                    nums2 = {1, 2, 3, 4, 5};
                    desc = "nums1 Empty (m=0)";
                    break;
                case 3: 
                    // 边界:nums2 为空 (n=0)
                    m = 5; n = 0;
                    nums1 = {1, 2, 3, 4, 5};
                    nums2 = {};
                    desc = "nums2 Empty (n=0)";
                    break;
                case 4: 
                    // 逻辑:nums2 所有元素都小于 nums1 (放在前面)
                    m = 5; n = 5;
                    nums1 = {10, 12, 14, 16, 18};
                    nums2 = {1, 3, 5, 7, 9};
                    desc = "nums2 all smaller than nums1";
                    break;
                case 5: 
                    // 逻辑:nums2 所有元素都大于 nums1 (放在后面)
                    m = 5; n = 5;
                    nums1 = {1, 3, 5, 7, 9};
                    nums2 = {10, 12, 14, 16, 18};
                    desc = "nums2 all larger than nums1";
                    break;
                case 6: 
                    // 逻辑:交叉混合,小数据
                    m = 10; n = 10;
                    nums1 = gen_random_sorted(m, 0, 20);
                    nums2 = gen_random_sorted(n, 0, 20);
                    desc = "Interleaved Small Range";
                    break;
                case 7: 
                    // 逻辑:包含负数
                    m = 20; n = 20;
                    nums1 = gen_random_sorted(m, -100, 100);
                    nums2 = gen_random_sorted(n, -100, 100);
                    desc = "With Negative Numbers";
                    break;
                case 8: 
                    // 逻辑:大量重复元素
                    m = 15; n = 15;
                    nums1 = gen_random_sorted(m, 5, 7); // 只有 5, 6, 7
                    nums2 = gen_random_sorted(n, 6, 8); // 只有 6, 7, 8
                    desc = "Many Duplicates";
                    break;
                case 9: 
                    // 逻辑:大跨度数值 (INT 范围)
                    m = 50; n = 50;
                    nums1 = gen_random_sorted(m, -100000, 100000);
                    nums2 = gen_random_sorted(n, -100000, 100000);
                    desc = "Large Values";
                    break;
                case 10:
                    // 边界:最大约束 m+n = 200
                    m = 100; n = 100;
                    nums1 = gen_random_sorted(m, -1000, 1000);
                    nums2 = gen_random_sorted(n, -1000, 1000);
                    desc = "Max Constraints (m+n=200)";
                    break;
            }
    
            // ---- 写入 .in 文件 ----
            // 格式:
            // m n
            // nums1 elements...
            // nums2 elements...
            
            fin << m << " " << n << endl;
            
            // 写入 nums1
            for (int k = 0; k < m; ++k) {
                fin << nums1[k] << (k == m - 1 ? "" : " ");
            }
            fin << endl; // 即使是空行也要换行,符合标准输入流习惯
    
            // 写入 nums2
            for (int k = 0; k < n; ++k) {
                fin << nums2[k] << (k == n - 1 ? "" : " ");
            }
            fin << endl;
    
            // ---- 计算并写入 .out 文件 ----
            vector<int> result = solver.solve(nums1, nums2);
            
            // 格式:
            // res1 res2 ...
            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 << " [m=" << m << ", n=" << n << "]" << endl;
        }
    
        cout << "\nAll 10 test cases generated successfully!" << endl;
        return 0;
    }
    

    使用与判题说明

    1. 输入文件格式

      • Line 1: m n
      • Line 2: m 个整数(nums1 的有效部分)
      • Line 3: n 个整数(nums2 的有效部分)
      • 注意:如果 m=0 或 n=0,对应的那一行会是一个空行(只包含换行符),这是符合 cin 读取逻辑的,也会被 getline 读为空串。
    2. 测试点覆盖

      • Case 1: 最小非空 (1,11, 1)。
      • Case 2, 3: 考察空数组 (m=0m=0n=0n=0) 的边界情况,防止代码中的循环条件写错。
      • Case 4, 5: 考察完全不重叠的区间合并(一个数组全小于另一个),验证指针移动逻辑。
      • Case 7: 包含负数,防止某些计数排序等非比较算法的错误实现。
      • Case 8: 包含大量重复,验证等号条件的稳定性。
      • Case 10: 满数据范围 (m+n=200m+n=200)。
    3. OJ 设置建议

      • 时间限制: 1000ms (实际上该题 N200N \le 200,即使是 O(N2)O(N^2) 的插入排序也能在 1ms 内跑完)。
      • 内存限制: 128MB。
      • 比对方式: 忽略行末空格和文末换行的文本比对 (Ignore trailing whitespace)。
    • 0
      @ 2025-12-17 18:02:42

      你好!作为你的OI竞赛金牌教练,我为你整理了这道题的 NOIP C++14 标准题解代码

      这段代码完全符合竞赛中**“读入-处理-输出”的完整流程,使用了逆向双指针**的最优解法,并包含了详细的思维推导和复杂度分析。


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

      /**
       * 题目:合并两个有序数组 (Merge Sorted Array)
       * 算法:逆向双指针 (Reverse Two Pointers) / 原地合并
       * 时间复杂度:O(m + n)
       * 空间复杂度:O(1) (不计算存储结果本身的 nums1 空间)
       * 风格:NOIP/CSP 竞赛标准
       */
      
      #include <iostream>
      #include <vector>
      #include <algorithm>
      
      using namespace std;
      
      // 开启 IO 优化,适应大规模数据输入
      void fast_io() {
          ios::sync_with_stdio(false);
          cin.tie(nullptr);
      }
      
      int main() {
          fast_io();
      
          // ==========================================
          // 1. 数据读入
          // ==========================================
          int m, n;
          // 检查输入流状态,增强代码鲁棒性
          if (!(cin >> m >> n)) return 0;
      
          // nums1 需要预留 m + n 的空间
          // 初始化时先读入前 m 个有效元素
          vector<int> nums1(m + n); 
          for (int i = 0; i < m; ++i) {
              cin >> nums1[i];
          }
          // 注意:nums1 的下标 [m, m+n-1] 现在的逻辑上是空的(或者默认为0)
          // 它们就是留给我们进行原地合并的“缓冲区”
      
          vector<int> nums2(n);
          for (int i = 0; i < n; ++i) {
              cin >> nums2[i];
          }
      
          // ==========================================
          // 2. 算法核心:逆向双指针
          // ==========================================
          
          // p1: 指向 nums1 初始有效数据的末尾
          int p1 = m - 1;
          // p2: 指向 nums2 初始有效数据的末尾
          int p2 = n - 1;
          // tail: 指向 nums1 总空间的末尾,这是我们填入数据的目标位置
          int tail = m + n - 1;
      
          // 当两个数组都还有元素未处理时,比较并填入大的那个
          while (p1 >= 0 && p2 >= 0) {
              // 【关键逻辑】从后往前填,大的沉底
              if (nums1[p1] > nums2[p2]) {
                  nums1[tail] = nums1[p1];
                  p1--; // nums1 的指针前移
              } else {
                  nums1[tail] = nums2[p2];
                  p2--; // nums2 的指针前移
              }
              tail--; // 目标位置前移
          }
      
          // ==========================================
          // 3. 边界处理 (易错点)
          // ==========================================
          // 循环结束后,可能出现两种情况:
          // Case A: p1 >= 0, p2 < 0。
          //    说明 nums2 的元素已经全部填完了,nums1 剩下的元素本身就在原地,
          //    而且是有序的,不需要动。
          
          // Case B: p2 >= 0, p1 < 0。
          //    说明 nums1 的初始元素全部移到了后面,nums2 还有剩余。
          //    我们需要把 nums2 剩下的元素拷贝到 nums1 的最前面。
          
          while (p2 >= 0) {
              nums1[tail] = nums2[p2];
              p2--;
              tail--;
          }
      
          // ==========================================
          // 4. 结果输出
          // ==========================================
          for (int i = 0; i < m + n; ++i) {
              cout << nums1[i] << (i == m + n - 1 ? "" : " ");
          }
          cout << endl;
      
          return 0;
      }
      

      复杂度分析与思考过程

      1. 时间复杂度分析

      • 思考过程
        • 我们的算法主要依赖于一个 while 循环以及后续的一个补充循环。
        • 在每次循环迭代中,指针 tail 都会减 1,必然会填充一个位置。
        • 总共有 m+nm + n 个位置需要填充。
        • 每个元素(无论是来自 nums1 还是 nums2)都只会被读取一次、写入一次。
        • 内部的操作(比较、赋值、自减)都是 O(1)O(1) 的。
      • 结论:时间复杂度为 O(m+n)O(m + n)。这是线性的,也是本题的理论下界。

      2. 空间复杂度分析

      • 思考过程
        • 题目要求 原地 (In-place) 修改。
        • 我们直接利用了 nums1 尾部闲置的 nn 个空间。
        • 我们只定义了三个整型变量指针 p1, p2, tail
        • 不需要开辟新的数组来暂存数据(不像归并排序通常需要 O(N)O(N) 的辅助空间)。
      • 结论:额外空间复杂度为 O(1)O(1)

      3. 为什么不从前往后合并?(优化建议与思维陷阱)

      • 陷阱:如果不使用额外空间,直接从前往后(即 p1=0, p2=0)开始比较:
        • 如果 nums2[0] < nums1[0],我们需要把 nums2[0] 放到 nums1[0] 的位置。
        • 但是 nums1[0] 原来的数据怎么办?它还没被处理,如果覆盖掉就丢失了。
        • 为了不丢失,只能把 nums1 中所有的元素整体向后挪一位,这会导致时间复杂度退化成 O(m×n)O(m \times n) 或者 O((m+n)2)O((m+n)^2),这在 m,nm, n 很大时是不可接受的。
      • 优化核心利用空闲空间。因为 nums1 的尾部是空的,在这个区域操作绝对安全,不会覆盖任何有效数据。这就是“逆向思维”的威力。

      易错点总结

      1. 循环条件:主循环条件是 && (与),即只要有一个数组空了就停下来。不要写成 ||,否则会导致越界访问。
      2. 剩余元素处理
        • 许多同学会忘记处理 p2 >= 0 的情况(即 nums2 还有剩)。
        • 也有同学会画蛇添足去处理 p1 >= 0 的情况。需要理解为什么 p1 剩下了不需要动(因为它们已经在目标位置了)。
      3. 指针越界:在使用数组下标 nums1[p1] 前,隐含的条件是 p1 >= 0。将判断放在 while 条件里可以保证安全。
      • 1

      信息

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