2 条题解
-
0
作为金牌教练,制作数据不仅要追求随机性,更要针对算法的边界条件进行“定点爆破”。
本题的核心在于哈希表的效率。我们需要构造能够覆盖“极长序列”、“极碎序列”、“大规模重复”以及“大数值差”的测试点。由于 且涉及哈希,生成器必须保证在 或 时间内完成计算,否则生成 组数据会非常缓慢。
以下是为你准备的自动化数据生成器。它集成了 标程逻辑,采用非递归写法,确保生成的每一组数据都有准确的
.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 或重复值 考察对边界和去重的处理。 4-5 连续递增/递减 考察 算法在最理想/最简单情况下的表现。 6 碎片化序列 核心考查点:考察是否正确过滤了非起点数字。如果逻辑不对(对每个数都往后搜),会退化到 。 7-8 大数值/大跨度 考察哈希表处理极大整数的稳健性(避免数组桶排序的“偷鸡”做法)。 9 80% 连续 + 20% 随机 压力测试:。测试 while循环在长序列下的效率。10 极限稀疏随机 压力测试:。测试 unordered_set的查找效率和抗哈希冲突能力。
三、 考场避坑建议 (针对本题)
-
关于
std::unordered_set的稳定性: 在 NOI 竞赛中,如果 很大且时间限制紧(如 0.5s),要注意哈希表的常数。如果发现程序变慢,检查是否是因为频繁的Rehash。- 技巧:在生成器和选手的代码中,建议使用
num_set.reserve(n)。这可以大幅减少动态分配内存的时间。
- 技巧:在生成器和选手的代码中,建议使用
-
避免使用
std::set: 如果选手使用了std::set(红黑树,),在测试点 9 和 10 可能会因为对 个元素进行 次查找而超时。 -
数据溢出: 虽然 在
int范围内,但num + 1在num等于 时会发生溢出。- 对策:在本题的逻辑中,建议使用
long long current_num = num;来进行current_num + 1的运算。
- 对策:在本题的逻辑中,建议使用
-
非递归安全: 本生成器和标程均不涉及递归,所有序列查询均通过
while迭代完成。即使序列长度达到 ,也不会触发系统栈溢出。
你可以直接运行此生成器,它会自动在当前目录下生成一套高质量的练习数据。加油!
-
信息
- ID
- 19386
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 1
- 已通过
- 1
- 上传者