2 条题解
-
0
这是为你准备的完整数据生成器。这份代码集成了绝对正确的解题逻辑(用于生成标准输出)和10种针对性测试数据生成策略。
它会直接在当前目录下生成
1.in/1.out到10.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; }使用与判题说明
-
输入文件格式:
- Line 1:
m n - Line 2:
m个整数(nums1的有效部分) - Line 3:
n个整数(nums2的有效部分) - 注意:如果 m=0 或 n=0,对应的那一行会是一个空行(只包含换行符),这是符合
cin读取逻辑的,也会被getline读为空串。
- Line 1:
-
测试点覆盖:
- Case 1: 最小非空 ()。
- Case 2, 3: 考察空数组 ( 或 ) 的边界情况,防止代码中的循环条件写错。
- Case 4, 5: 考察完全不重叠的区间合并(一个数组全小于另一个),验证指针移动逻辑。
- Case 7: 包含负数,防止某些计数排序等非比较算法的错误实现。
- Case 8: 包含大量重复,验证等号条件的稳定性。
- Case 10: 满数据范围 ()。
-
OJ 设置建议:
- 时间限制: 1000ms (实际上该题 ,即使是 的插入排序也能在 1ms 内跑完)。
- 内存限制: 128MB。
- 比对方式: 忽略行末空格和文末换行的文本比对 (Ignore trailing whitespace)。
-
-
0
你好!作为你的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,必然会填充一个位置。 - 总共有 个位置需要填充。
- 每个元素(无论是来自
nums1还是nums2)都只会被读取一次、写入一次。 - 内部的操作(比较、赋值、自减)都是 的。
- 我们的算法主要依赖于一个
- 结论:时间复杂度为 。这是线性的,也是本题的理论下界。
2. 空间复杂度分析
- 思考过程:
- 题目要求 原地 (In-place) 修改。
- 我们直接利用了
nums1尾部闲置的 个空间。 - 我们只定义了三个整型变量指针
p1,p2,tail。 - 不需要开辟新的数组来暂存数据(不像归并排序通常需要 的辅助空间)。
- 结论:额外空间复杂度为 。
3. 为什么不从前往后合并?(优化建议与思维陷阱)
- 陷阱:如果不使用额外空间,直接从前往后(即
p1=0, p2=0)开始比较:- 如果
nums2[0] < nums1[0],我们需要把nums2[0]放到nums1[0]的位置。 - 但是
nums1[0]原来的数据怎么办?它还没被处理,如果覆盖掉就丢失了。 - 为了不丢失,只能把
nums1中所有的元素整体向后挪一位,这会导致时间复杂度退化成 或者 ,这在 很大时是不可接受的。
- 如果
- 优化核心:利用空闲空间。因为
nums1的尾部是空的,在这个区域操作绝对安全,不会覆盖任何有效数据。这就是“逆向思维”的威力。
易错点总结
- 循环条件:主循环条件是
&&(与),即只要有一个数组空了就停下来。不要写成||,否则会导致越界访问。 - 剩余元素处理:
- 许多同学会忘记处理
p2 >= 0的情况(即nums2还有剩)。 - 也有同学会画蛇添足去处理
p1 >= 0的情况。需要理解为什么p1剩下了不需要动(因为它们已经在目标位置了)。
- 许多同学会忘记处理
- 指针越界:在使用数组下标
nums1[p1]前,隐含的条件是p1 >= 0。将判断放在while条件里可以保证安全。
- 思考过程:
- 1
信息
- ID
- 19351
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 1
- 已通过
- 1
- 上传者