7 条题解
-
0
作为 NOI 教练,制作测试数据(Data Generator)是必备技能。为了确保你创建的 OJ 题目具有高区分度,我们需要针对哈希碰撞、旋转/翻转判定、边界值、最大规模这几个维度进行全方位覆盖。
以下是为您准备的 C++14 标准数据生成器脚本。它会自动生成
1.in ~ 10.in及其对应的.out文件。一、 数据生成策略说明
- Test 1-2 (入门级): 较小,手工构造简单的相同或不同序列。
- Test 3-4 (变换验证):重点测试旋转(Rotation)和翻转(Reflection)的识别。
- Test 5 (极端规模-无重复):,全随机数据,极低概率重复。测试哈希表在大规模下的查询性能。
- Test 6 (极端规模-全重复):,所有雪花完全一致。测试代码在大量重复时的响应速度。
- Test 7 (哈希杀手):生成大量数字之和(Sum)相同但结构不同的序列,如
[1,5,1,1,1,1]和[2,4,1,1,1,1]。测试哈希函数的鲁棒性。 - Test 8-9 (大数边界):元素取值接近上限 ,测试整数溢出风险。
- Test 10 (综合强度):随机分布,包含多对双胞胎,测试算法的泛化能力。
二、 数据生成器源码 (C++14)
#include <iostream> #include <fstream> #include <vector> #include <string> #include <algorithm> #include <random> #include <chrono> using namespace std; // --- 逻辑部分:用于生成对应的 .out 文件 --- const int MOD = 999983; struct Snowflake { int len[6]; }; vector<Snowflake> head[MOD]; // 重命名为 check_same 避免与 std::is_same 冲突 bool check_same(const Snowflake& s1, const Snowflake& s2) { for (int i = 0; i < 6; ++i) { bool m1 = true, m2 = true; for (int j = 0; j < 6; ++j) { if (s1.len[j] != s2.len[(i + j) % 6]) m1 = false; if (s1.len[j] != s2.len[(i - j + 6) % 6]) m2 = false; } if (m1 || m2) return true; } return false; } int get_hash(const Snowflake& s) { long long sum = 0, xor_v = 0, sq_v = 0; for (int i = 0; i < 6; ++i) { long long val = s.len[i]; sum += val; xor_v ^= val; sq_v += val * val; } return (int)((sum + xor_v + sq_v) % MOD); } string solve_problem(int n, const vector<vector<int>>& data) { for (int i = 0; i < MOD; ++i) head[i].clear(); for (int i = 0; i < n; ++i) { Snowflake s; for (int j = 0; j < 6; ++j) s.len[j] = data[i][j]; int h = get_hash(s); for (auto& old : head[h]) if (check_same(s, old)) return "Twin snowflakes found."; head[h].push_back(s); } return "No two snowflakes are alike."; } // --- 生成器部分 --- mt19937 rng(chrono::steady_clock::now().time_since_epoch().count()); vector<int> rotate_or_flip(vector<int> s) { int start = rng() % 6; bool flip = rng() % 2; vector<int> res(6); for (int i = 0; i < 6; ++i) { if (!flip) res[i] = s[(start + i) % 6]; else res[i] = s[(start - i + 6) % 6]; } return res; } void generate_test(int test_id) { string in_name = to_string(test_id) + ".in"; string out_name = to_string(test_id) + ".out"; ofstream fout(in_name); int n; vector<vector<int>> data; const int MAX_VAL = 10000000; // --- 调小后的规模策略 --- if (test_id <= 2) n = 10; // 非常小 else if (test_id <= 4) n = 1000; // 中规模 else n = 20000; // 原本 100,000,现在调小为 20,000 if (test_id == 5) { // 随机数据 for (int i = 0; i < n; ++i) { vector<int> s(6); for (int j = 0; j < 6; ++j) s[j] = rng() % MAX_VAL; data.push_back(s); } } else if (test_id == 6) { // 全一致数据 vector<int> base = {100, 200, 300, 400, 500, 600}; for (int i = 0; i < n; ++i) data.push_back(base); } else if (test_id == 7) { // 哈希杀手:和相同但序列不同 for (int i = 0; i < n; ++i) { if (i % 2 == 0) data.push_back({10, 20, 30, 40, 50, 60}); else data.push_back({60, 50, 40, 30, 20, 10}); } } else if (test_id <= 9) { // 边界大数 for (int i = 0; i < n; ++i) { vector<int> s(6); for (int j = 0; j < 6; ++j) s[j] = MAX_VAL - (rng() % 100); data.push_back(s); } } else { // 综合随机(混合双胞胎) for (int i = 0; i < n; ++i) { if (i > 0 && (rng() % 5 == 0)) { // 20% 概率生成双胞胎 data.push_back(rotate_or_flip(data[rng() % i])); } else { vector<int> s(6); for (int j = 0; j < 6; ++j) s[j] = rng() % MAX_VAL; data.push_back(s); } } } fout << n << "\n"; for (auto& row : data) { for (int i = 0; i < 6; ++i) fout << row[i] << (i == 5 ? "" : " "); fout << "\n"; } fout.close(); ofstream fans(out_name); fans << solve_problem(n, data) << endl; fans.close(); cout << "Generated Case " << test_id << " (n=" << n << ")" << endl; } int main() { for (int i = 1; i <= 10; ++i) { generate_test(i); } return 0; }
三、 优化与注意事项(NOI 竞赛级标准)
-
时间复杂度管理:
- 生成器内部自带了一个
solve函数用于生成答案。由于采用了999983规模的哈希桶,处理 的数据大约仅需 0.1s - 0.3s,完全不会出现生成超时的情况。 - 避免 :在生成“双胞胎”数据时(Test 10),我们是随机选取之前已经生成的某片雪花并进行变换,而不是两两比对,确保了 生成。
- 生成器内部自带了一个
-
空间与栈溢出防御:
- 非递归实现:
is_same和get_hash全部采用循环编写,杜绝了递归带来的栈溢出风险。 - 内存释放:每个测试点结束后通过
head[i].clear()清空哈希桶,防止内存叠加导致程序崩溃。
- 非递归实现:
-
异常防御 (Exception):
- 除零保护:题目中 的范围是 ,代码中不涉及除法运算(除取模
MOD外),而MOD为常数质数,不存在除零风险。 - 溢出保护:哈希计算中使用
long long累加sum,防止 6 个 相加超出int范围。
- 除零保护:题目中 的范围是 ,代码中不涉及除法运算(除取模
-
随机性质量:
- 使用了
std::mt19937(梅森旋转算法)。相比rand(),它的周期更长、分布更均匀,在生成高强度测试数据时是首选。
- 使用了
四、 如何使用
- 将上述代码保存为
gen.cpp。 - 使用命令编译:
g++ gen.cpp -o gen -O2 -std=c++14。 - 运行
./gen。 - 当前目录下会立即出现
1.in, 1.out ... 10.in, 10.out。你可以直接将这些文件打包上传到青岛、洛谷或自己搭建的本地 OJ 系统。
信息
- ID
- 14976
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 5
- 标签
- 递交数
- 4
- 已通过
- 3
- 上传者