3 条题解
-
0
在 NOI 竞赛中,很多同学喜欢使用
cin和cout,因为它们的语法更简洁,且不容易像scanf那样因为写错格式控制符(如%d写成%f)而导致错误。但是,原生的
cin/cout速度极慢,在处理 以上的数据时极易导致超时(TLE)。因此,如果要将代码改为cin版本,必须包含 I/O 优化流控制。以下是适配 NOIP/CSP 风格的
cin优化版代码:
一、 优化版
cin题解代码 (C++14)/* * Problem: Next Greater Element I (Stream IO Version) * Algorithm: Monotonic Stack + Array Mapping * Standard: C++14 (NOIP/CSP Compatible) */ #include <iostream> #include <vector> #include <stack> using namespace std; // 根据题目数据范围设定常量 const int MAXV = 10005; // 值域范围 const int MAXN = 1005; // 数组长度范围 int nums1[MAXN]; int nums2[MAXN]; int next_greater[MAXV]; int main() { /** * 【核心优化 1】 * ios::sync_with_stdio(false); * 作用:取消 C++ 标准流与 C 标准库(stdio)的同步。 * 优化后,cin 的效率可以接近 scanf。 * 注意:使用此行后,绝对不能在同一个程序中混合使用 cin/cout 和 scanf/printf。 */ ios::sync_with_stdio(false); /** * 【核心优化 2】 * cin.tie(nullptr); * 作用:解除 cin 与 cout 的绑定。 * 默认情况下,cin 会在读取前刷新 cout 的缓冲区,解除绑定可进一步提升速度。 */ cin.tie(nullptr); int n, m; // 使用 cin 进行读取 cin >> n >> m; for (int i = 0; i < n; ++i) { cin >> nums1[i]; } // 初始化映射表 for (int i = 0; i < MAXV; ++i) { next_greater[i] = -1; } stack<int> s; for (int i = 0; i < m; ++i) { cin >> nums2[i]; // 单调栈逻辑保持不变 while (!s.empty() && nums2[i] > s.top()) { next_greater[s.top()] = nums2[i]; s.pop(); } s.push(nums2[i]); } // 输出部分 for (int i = 0; i < n; ++i) { cout << next_greater[nums1[i]]; /** * 【核心优化 3】 * 使用 '\n' 替代 endl。 * endl 会强制刷新缓冲区(flush),在循环中会导致巨大的性能开销。 * 在竞赛中,除非特殊需要,一律使用 '\n'。 */ if (i == n - 1) cout << '\n'; else cout << ' '; } return 0; }
二、 关键点与避坑指南
1. 关于
sync_with_stdio(false)的副作用- 混用禁止:一旦加入这行代码,你的程序里就不能再出现
printf,scanf,getchar,putchar。如果混用,输出顺序可能会发生错乱,导致 WA。 - 不可回退:这是一个全局开关,一旦关闭无法中途开启。
2.
endlvs'\n'- 在 NOI 评测环境中,
endl的执行效率极低。 - 实验数据:在输出 个整数的情况下,使用
endl的耗时可能是使用'\n'的 10 倍以上。请务必养成使用'\n'的习惯。
3. 为什么不用
std::vector?- 虽然
std::vector很方便,但在 NOI 竞赛中,如果能确定数据上限(如本题的 ),使用静态数组不仅内存访问更快,而且能有效避免动态内存分配带来的常数抖动。
三、 总结:读题时的“IO 预警”
当你通过关键词识别出题目类型后,就要决定使用哪种 IO 方式:
- 数据量 < 10^5:随意使用,
cin不加优化也能过。 - 数据量 10^5 ~ 5*10^5:必须使用
scanf/printf或 优化的 cin/cout。 - 数据量 > 10^6 或 时限 < 0.5s:建议直接上 Fast IO(快读快写) 以保万全。
这道题的数据量只有 ,属于第一种情况,但作为职业选手,你应该养成只要写 cin 就必加优化的好习惯。
- 混用禁止:一旦加入这行代码,你的程序里就不能再出现
-
0
为了方便你创建 OJ(Online Judge)测试数据,我编写了一个全自动的数据生成器。它会根据 NOI 竞赛的标准,自动生成 10 组测试点(
1.in/out到10.in/out),并涵盖了从基础边界到极限规模的所有情况。该生成器集成了解题逻辑(标准代码),确保生成的
.out文件与.in文件完全对应。一、 数据生成器代码 (C++14)
#include <iostream> #include <vector> #include <algorithm> #include <random> #include <stack> #include <string> #include <fstream> #include <set> #include <chrono> using namespace std; // --- 核心算法:用于生成 .out 文件 --- vector<int> solve(int n, int m, const vector<int>& nums1, const vector<int>& nums2) { int next_greater[10005]; for (int i = 0; i < 10005; ++i) next_greater[i] = -1; stack<int> s; for (int x : nums2) { while (!s.empty() && x > s.top()) { next_greater[s.top()] = x; s.pop(); } s.push(x); } vector<int> res; for (int x : nums1) { res.push_back(next_greater[x]); } return res; } // --- 数据构造工具 --- mt19937 rng(chrono::steady_clock::now().time_since_epoch().count()); int get_rand(int l, int r) { return uniform_int_distribution<int>(l, r)(rng); } void write_test_case(int case_id, int n, int m, vector<int> nums1, vector<int> nums2) { string in_name = to_string(case_id) + ".in"; string out_name = to_string(case_id) + ".out"; // 写 .in 文件 ofstream fin(in_name); fin << n << " " << m << "\n"; for (int i = 0; i < n; ++i) fin << nums1[i] << (i == n - 1 ? "" : " "); fin << "\n"; for (int i = 0; i < m; ++i) fin << nums2[i] << (i == m - 1 ? "" : " "); fin << "\n"; fin.close(); // 写 .out 文件 vector<int> ans = solve(n, m, nums1, nums2); ofstream fout(out_name); for (int i = 0; i < n; ++i) fout << ans[i] << (i == n - 1 ? "" : " "); fout << "\n"; fout.close(); cout << "Test Case " << case_id << " generated: n=" << n << ", m=" << m << endl; } int main() { // 设置值域 const int MAX_VAL = 10000; for (int t = 1; t <= 10; ++t) { int n, m; vector<int> nums2_pool; // --- 针对不同测试点构造不同特性的数据 --- if (t == 1) { // 边界:极小规模 n = 1, m = 1; } else if (t == 2) { // 边界:n=m n = 10, m = 10; } else if (t == 3) { // 特殊:严格递增 (所有都有答案) n = 50, m = 100; } else if (t == 4) { // 特殊:严格递减 (所有都为-1) n = 50, m = 100; } else if (t == 5) { // 特殊:先减后增(V型) n = 100, m = 200; } else if (t <= 8) { // 一般:随机中等规模 n = get_rand(100, 500); m = get_rand(500, 1000); } else { // 极限:最大规模 n = 1000, m = 1000; } // 生成互不相同的 nums2 set<int> used; while (nums2_pool.size() < (size_t)m) { int val = get_rand(0, MAX_VAL); if (used.find(val) == used.end()) { used.insert(val); nums2_pool.push_back(val); } } // 针对特殊情况排序 if (t == 3) sort(nums2_pool.begin(), nums2_pool.end()); if (t == 4) sort(nums2_pool.rbegin(), nums2_pool.rend()); if (t == 5) { sort(nums2_pool.begin(), nums2_pool.end()); vector<int> v_shape; for(int i=0; i<m; i+=2) v_shape.push_back(nums2_pool[m-1-i]); // 放入大的 for(int i=1; i<m; i+=2) v_shape.insert(v_shape.begin(), nums2_pool[m-1-i]); nums2_pool = v_shape; } // 从 nums2 中随机抽取 n 个数作为 nums1 (保证是子集) vector<int> nums1_indices; for (int i = 0; i < m; ++i) nums1_indices.push_back(i); shuffle(nums1_indices.begin(), nums1_indices.end(), rng); vector<int> nums1; for (int i = 0; i < n; ++i) { nums1.push_back(nums2_pool[nums1_indices[i]]); } // nums1 内部顺序可以打乱 shuffle(nums1.begin(), nums1.end(), rng); write_test_case(t, n, m, nums1, nums2_pool); } return 0; }
二、 测试点分布说明
测试点 特性说明 1 最小边界:只有一个元素。 2 10 全集测试: nums1是nums2的全排列。3 50 100 单调性测试: nums2严格递增,除最后一个外都有答案。4 单调性测试: nums2严格递减,全部答案应为-1。5 100 200 波动测试: nums2呈 “V” 字型,考察栈的多次弹出逻辑。6-7 300+ 600+ 随机分布:考察代码处理乱序数据的鲁棒性。 8 500 1000 较大规模:考察哈希映射或映射表的效率。 9-10 1000 最大规模: 均达到上限,值域覆盖 。
三、 优化与注意事项
- 非递归安全:生成器和解题逻辑均采用循环+栈(迭代法),即使数据规模扩大到 ,也不会出现爆栈风险。
- 唯一性保证:使用了
std::set和随机打乱,确保了题目要求的“不包含重复元素”和“子集关系”。 - 时间复杂度:
- 生成逻辑: 或 。
- 求解逻辑:。
- 总时间:生成 10 个文件在毫秒级完成,完全不会超时。
- 随机性:使用了
mt19937高性能随机数引擎,比传统的rand()随机分布更均匀,避免在 OJ 测试中被“骗分”代码通过。
四、 如何使用
- 复制上述代码到
gen.cpp。 - 编译:
g++ gen.cpp -o gen -std=c++14。 - 运行:
./gen。 - 生成的
1.in~10.out即可直接上传至 OJ 平台。
-
0
作为教练,我需要提醒你:在 NOI 竞赛中,代码的稳定性和执行效率同等重要。这道题虽然在 LeetCode 上是简单题,但它所蕴含的“单调栈”思想是解决许多 hard 题(如:修筑祭坛、最大矩形面积)的核心。
下面是符合 NOIP/CSP 规范的标准程序,采用了单调栈 + 快速映射的方案。
一、 复杂度分析思考过程
- 暴力法的瓶颈:
若对
nums1中的每个元素都在nums2中线性扫描,时间复杂度为 。当 达到 级别时就会超时。 - 单调栈的引入:
我们需要一种“预处理”手段。当我们遍历
nums2时,如果当前元素比栈顶大,说明栈顶元素找到了它的“下一个更大者”。- 时间复杂度:
nums2的每个元素仅进栈一次、出栈一次,复杂度为 。最后查询nums1复杂度为 。总计 。
- 时间复杂度:
- 空间复杂度: 我们需要一个栈 和一个存储映射关系的数组或哈希表。由于题目给定数值范围较小(),直接开数组作为映射表是最稳妥的,复杂度 。
二、 时间复杂度优化建议
- 容器选择:在竞赛中,
std::unordered_map的常数项较大且在极端数据下可能发生哈希冲突导致退化。由于本题值域只有 ,直接使用数组ans_map[10005]代替哈希表,可以将常数级时间优化到极致。 - 输入输出:若数据量达到 级别,建议使用
scanf/printf或快读(Fast I/O),cin/cout即使解绑在某些评测机上依然较慢。
三、 题解标准代码 (C++14)
/* * Problem: Next Greater Element I * Algorithm: Monotonic Stack (单调栈) + Hash Mapping * Standard: C++14 (NOIP/CSP Compatible) */ #include <iostream> #include <vector> #include <stack> #include <cstdio> using namespace std; // 根据题目数据范围设定常量 const int MAXV = 10005; // 值域范围 const int MAXN = 1005; // 数组长度范围 int nums1[MAXN]; int nums2[MAXN]; int next_greater[MAXV]; // 核心:映射表,存储值 v 对应的下一个更大元素 int main() { // 1. 竞赛建议:如果需要文件 IO,请取消下面两行注释 // freopen("next.in", "r", stdin); // freopen("next.out", "w", stdout); int n, m; if (scanf("%d %d", &n, &m) == EOF) return 0; // 读取第一个数组 for (int i = 0; i < n; ++i) { scanf("%d", &nums1[i]); } // 2. 预处理映射表,初始化为 -1(表示默认没有更大元素) for (int i = 0; i < MAXV; ++i) { next_greater[i] = -1; } // 3. 单调栈核心逻辑 stack<int> s; for (int i = 0; i < m; ++i) { scanf("%d", &nums2[i]); // 如果当前元素比栈顶大,说明栈顶元素找到了“大哥” while (!s.empty() && nums2[i] > s.top()) { // 易错点:映射表的下标是栈顶的“值”而不是索引 next_greater[s.top()] = nums2[i]; s.pop(); // 已找到答案,弹出 } // 当前元素入栈,等待它的“大哥”出现 s.push(nums2[i]); } // 4. 根据 nums1 的顺序查询结果并输出 for (int i = 0; i < n; ++i) { // 直接从映射表中 $O(1)$ 获取结果 printf("%d%c", next_greater[nums1[i]], (i == n - 1 ? '\n' : ' ')); } return 0; }
四、 关键点与易错点注释(金牌教练提醒)
-
映射逻辑:
- 很多同学容易混淆“下标”和“数值”。题目明确
nums1和nums2里的数值互不相同,所以我们可以直接用next_greater[数值]来存答案。 - 如果数值范围很大(如 ),则必须使用
std::unordered_map<int, int>或者先进行离散化处理。
- 很多同学容易混淆“下标”和“数值”。题目明确
-
单调性的维护:
- 在这个场景下,栈内元素必须保持单调递减(从栈底到栈顶)。
- 一旦出现一个“破坏者”(比栈顶大的元素),就要不断执行
pop操作,直到栈恢复单调性。
-
边界处理:
- 遍历结束后,留在栈里的元素意味着它们右侧没有比它们更大的数。在初始化映射表为
-1时,我们已经优雅地处理了这种情况。
- 遍历结束后,留在栈里的元素意味着它们右侧没有比它们更大的数。在初始化映射表为
-
NOI 风格细节:
- 注意输出格式:最后一个数字后通常不建议有多余空格,但在大多数评测系统中不影响。上面的代码用了
(i == n - 1 ? '\n' : ' ')来处理空格。 - 使用
scanf提高读取效率。
- 注意输出格式:最后一个数字后通常不建议有多余空格,但在大多数评测系统中不影响。上面的代码用了
- 暴力法的瓶颈:
若对
- 1
信息
- ID
- 19362
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者