2 条题解
-
0
对于建设 OJ(Online Judge)而言,数据的严密性是第一位的。本数据生成器采用 C++14 标准,结合了随机分布与构造法,确保涵盖了所有边界条件(如 矩阵、 为周期的倍数、单行/单列矩阵等)。
一、 数据生成器代码 (C++14)
此脚本运行后将自动在当前目录下生成
1.in/1.out到10.in/10.out。#include <iostream> #include <fstream> #include <vector> #include <algorithm> #include <random> #include <string> #include <chrono> using namespace std; // --- 标程:用于生成 .out 文件 --- void generateOutput(int m, int n, int k, const vector<vector<int>>& grid, string out_name) { int total = m * n; k %= total; vector<vector<int>> res(m, vector<int>(n)); for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { int old_idx = i * n + j; int new_idx = (old_idx + k) % total; res[new_idx / n][new_idx % n] = grid[i][j]; } } ofstream fout(out_name); for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { fout << res[i][j] << (j == n - 1 ? "" : " "); } fout << "\n"; } fout.close(); } // --- 生成器逻辑 --- void make_data() { // 使用当前时间作为随机数种子 mt19937 rng(static_cast<unsigned int>(chrono::steady_clock::now().time_since_epoch().count())); // 元素值分布:-1000 到 1000 uniform_int_distribution<int> val_dist(-1000, 1000); for (int t = 1; t <= 10; ++t) { int m, n, k; // 测试点针对性构造 if (t == 1) { m = 1; n = 1; k = 1; } else if (t == 2) { m = 2; n = 3; k = 6; } else if (t == 3) { m = 5; n = 5; k = 0; } else if (t == 4) { m = 1; n = 50; k = 25; } else if (t == 5) { m = 50; n = 1; k = 100; } else if (t == 6) { m = 10; n = 10; k = 77; } else if (t == 7) { m = 40; n = 30; k = 1; } else if (t == 8) { m = 50; n = 50; k = 99; } else { m = 50; n = 50; k = 100; } vector<vector<int>> grid(m, vector<int>(n)); string in_name = to_string(t) + ".in"; string out_name = to_string(t) + ".out"; ofstream fin(in_name); fin << m << " " << n << " " << k << "\n"; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { // 【关键修正点】:这里传 rng,而不是 rng() grid[i][j] = val_dist(rng); fin << grid[i][j] << (j == n - 1 ? "" : " "); } fin << "\n"; } fin.close(); // 实时生成对应的标准输出 generateOutput(m, n, k, grid, out_name); cout << "Successfully generated " << in_name << " and " << out_name << endl; } } int main() { make_data(); return 0; }
二、 测试点规划说明
测试点序号 规模 的特征 考查点 1 最小规模边界,防止公式中出现除以 0 等异常(如果 处理不当)。 2 恰好等于总数。迁移后应与原矩阵完全一致。 3 的情况,验证代码对“零移动”的健壮性。 4 单行矩阵。考查一维索引计算是否仅依赖列数 。 5 单列矩阵。考查从行尾跳转到下一行行首的逻辑。 6 中等规模。检查基础逻辑正确性。 7 较大规模。只移动一次,测试“蛇形循环”的细微操作。 8 满额规模。检查是否存在数组下标越界或偏移计算错误。 9 满额规模。随机负数值分布,检查对负数处理的兼容性。 10 满额规模。高强度数据,用于压测时空效率。
三、 复杂度与安全性分析
- 时间复杂度思考:
- 本生成器包含“读取-计算-写入”三个环节。
- 对于每组数据,操作次数为 。总共 10 组数据,总计算量约为 次。
- 生成速度极快(通常在 0.1 秒内),完全符合 OJ 建设要求。
- 空间复杂度思考:
- 使用
vector<vector<int>>存储 的矩阵,占用内存约为 KB 左右。 - 没有使用递归,不存在爆栈风险。
- 使用
- 优化建议:
- 大 适应性:虽然原题中 ,但如果后续扩展到 ,生成器中的
int k应改为long long,并且标程中的k %= total逻辑依然能保持 的处理效率。 - 文件流缓冲:对于更大型的数据生成(如 ),可以加入
std::ios::sync_with_stdio(false)来加速文件的写入。
- 大 适应性:虽然原题中 ,但如果后续扩展到 ,生成器中的
四、 如何使用
- 复制上述代码存为
gen.cpp。 - 使用命令
g++ gen.cpp -o gen -O2 -std=c++14编译。 - 运行
./gen。 - 生成的
.in和.out文件即为标准的 NOI 格式数据,可直接上传至任意在线评测系统。
- 时间复杂度思考:
-
0
这是“二维网格迁移”问题的标准题解。在 NOI 竞赛中,这类题目通常被归类为“模拟”或“数学变换”。
一、 题解标准代码 (C++14)
#include <iostream> #include <vector> using namespace std; // 常量定义,50x50 的矩阵,数组开稍大一点(55x55)以保安全 int grid[55][55]; int res[55][55]; int m, n, k; int main() { // 提高 I/O 效率 ios::sync_with_stdio(false); cin.tie(0); // 1. 输入数据 if (!(cin >> m >> n >> k)) return 0; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { cin >> grid[i][j]; } } // 2. 核心数学转换 // 关键点:总元素个数是总周期。k 次迁移等同于 k % (m * n) 次迁移。 // 即使题目给的 k 很大(如 10^9),取模后也能瞬间解决。 int total = m * n; k %= total; // 3. 遍历原矩阵,直接计算迁移后的目标位置 for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { // 将二维坐标 (i, j) 映射为一维索引 int old_idx = i * n + j; // 计算迁移 k 次后的一维新索引 // 易错点:必须对 total 取模,处理最后一位跳回开头的情况 int new_idx = (old_idx + k) % total; // 将一维新索引还原为二维坐标 (new_r, new_c) // 易错点:除法得行,取模得列。注意除数是列数 n 而非 m。 int new_r = new_idx / n; int new_c = new_idx % n; res[new_r][new_c] = grid[i][j]; } } // 4. 输出结果 for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { cout << res[i][j] << (j == n - 1 ? "" : " "); } cout << "\n"; } return 0; }
二、 复杂度分析思考过程
1. 时间复杂度
- 读取输入:。
- 坐标转换与填充:我们只对矩阵进行了一次遍历。每个元素的新坐标计算是常数阶 的数学运算。
- 输出结果:。
- 总复杂度:。
- 在竞赛数据范围 下,运算次数仅为 2500 次左右,远低于 1 秒限制。
- 即使 扩大到 ,总复杂度 依然能在 10ms 内完成。
2. 空间复杂度
- 存储矩阵:我们需要
grid和res两个数组,空间复杂度为 。 - 额外空间:仅使用了几个整型变量( 等),空间复杂度为 。
- 总空间:。在 规模下消耗内存微乎其微。
三、 算法优化建议
虽然 已经是这类问题的理论下界(因为每个元素至少要处理一次),但在特定的竞赛场景下仍有优化点:
-
原地翻转(In-place Rotation):
- 思路:如果不允许使用辅助数组
res,可以将此题看作“一维数组循环右移 位”。 - 技巧:
- 翻转整个一维化的数组。
- 翻转前 个元素。
- 翻转剩余的元素。
- 效果:空间复杂度降至 (不计输入存储),这在内存限制极其苛刻(如 1MB)的古老题目或嵌入式比赛中很有用。
- 思路:如果不允许使用辅助数组
-
大 值处理:
- 易错点:如果 的范围是 (超出
int),在执行k % total前必须使用long long类型存储 ,否则会发生溢出导致结果错误。
- 易错点:如果 的范围是 (超出
-
输出优化:
- 对于大规模矩阵输出,使用
cout << "\n"替代cout << endl。因为endl会强制刷新缓冲区,在输出频繁时会导致速度变慢。
- 对于大规模矩阵输出,使用
四、 总结:避坑指南
- 行列混淆:在还原坐标时,很多新手会写错
new_r = new_idx / m。记住:一维索引转换始终是以“每一行有多少列”即 作为基准的。 - 或 是总数倍数:利用
k %= total可以完美避开这类特殊情况的讨论。 - 下标从0开始:在竞赛中,建议统一使用 0 到 的下标体系,这与取模运算(Modulo)的数学特性完美契合。如果题目要求 1 索引,可以在输出时调整,而不要在逻辑运算中使用 1 索引。
- 1
信息
- ID
- 19354
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 1
- 已通过
- 1
- 上传者