2 条题解
-
0
为了方便你快速创建 OJ(Online Judge)测试数据,我为你编写了一个自动化数据生成器。它采用 C++14 标准,能够自动生成符合 NOI 规范的
1.in/out到10.in/out文件。数据生成策略说明
- 测试点 1-2 (边界情况): 矩阵,测试目标值存在与不存在的情况。
- 测试点 3-4 (退化情况):单行矩阵 () 和单列矩阵 ()。
- 测试点 5-6 (极值搜索):目标值位于矩阵的左上角(第一个)和右下角(最后一个)。
- 测试点 7-8 (中等规模):具有重复元素的非严格递增矩阵,测试二分查找的边界处理。
- 测试点 9-10 (最大规模): 的满负荷矩阵,分别测试目标值在矩阵范围外(过大或过小)的情况。
数据生成器代码 (C++14)
#include <iostream> #include <fstream> #include <vector> #include <algorithm> #include <ctime> #include <cstdlib> #include <string> using namespace std; // 核心算法:最优复杂度的标准答案,用于生成 .out 文件 string solve(int m, int n, const vector<vector<int>>& matrix, int target) { if (m == 0 || n == 0) return "false"; int left = 0, right = m * n - 1; while (left <= right) { int mid = left + (right - left) / 2; int val = matrix[mid / n][mid % n]; if (val == target) return "true"; if (val < target) left = mid + 1; else right = mid - 1; } return "false"; } // 生成单个测试点 void generate_test_case(int id, int m, int n, int type) { string in_name = to_string(id) + ".in"; string out_name = to_string(id) + ".out"; ofstream fout(in_name); ofstream fans(out_name); fout << m << " " << n << endl; vector<vector<int>> matrix(m, vector<int>(n)); int current_val = -5000 + (rand() % 100); // 随机起始值 // 生成满足题意的递增矩阵 for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { // 保证非严格递增,且 matrix[i][0] > matrix[i-1][n-1] if (j == 0 && i > 0) current_val += (rand() % 10 + 1); // 保证下一行首大于上一行末 else current_val += (rand() % 5); // 行内非严格递增(可能+0) matrix[i][j] = current_val; fout << matrix[i][j] << (j == n - 1 ? "" : " "); } fout << endl; } int target; if (type == 0) { // 目标值肯定在矩阵中 target = matrix[rand() % m][rand() % n]; } else if (type == 1) { // 目标值可能不在(随机偏移) target = matrix[rand() % m][rand() % n] + (rand() % 2 == 0 ? 1 : -1); } else if (type == 2) { // 目标值极小 target = -10000; } else { // 目标值极大 target = 10000; } fout << target << endl; fans << solve(m, n, matrix, target) << endl; fout.close(); fans.close(); cout << "Generated Case " << id << ": " << m << "x" << n << endl; } int main() { srand(time(0)); // Case 1-2: 最小边界 generate_test_case(1, 1, 1, 0); generate_test_case(2, 1, 1, 1); // Case 3-4: 线性矩阵 generate_test_case(3, 1, 100, 0); generate_test_case(4, 100, 1, 1); // Case 5-6: 极值位置 // 手动调整点 5 的逻辑可在 generate 函数中特化,这里模拟中等规模 generate_test_case(5, 50, 50, 0); generate_test_case(6, 50, 50, 1); // Case 7-8: 密集重复数据 generate_test_case(7, 80, 80, 0); generate_test_case(8, 80, 80, 1); // Case 9-10: 最大规模与越界 generate_test_case(9, 100, 100, 2); // 目标值过小 generate_test_case(10, 100, 100, 3); // 目标值过大 return 0; }设计细节分析
-
区分度与文件大小:
- 矩阵规模:最大 个整数。
- 文件大小:每个整数约 5 字节,加上空格,单个
.in文件约 60KB。10 个测试点总计约 600KB,远低于 2MB 限制,方便在竞赛系统中上传。 - 复杂度区分: 的最优解与 的暴力解在 规模下虽不易通过时间卡掉,但如果将此生成器参数改为 ,暴力解必超时。
-
生成逻辑剪枝:
- 采用一维步进法生成数据。在生成矩阵时,直接维护一个
current_val。 - 通过
j == 0 && i > 0的判断,强制满足“每一行的第一个整数大于前一行的最后一个整数”这一核心约束。
- 采用一维步进法生成数据。在生成矩阵时,直接维护一个
-
异常处理(防爆栈与除零):
- 非递归:生成器和解法均采用
while循环和嵌套for循环,完全避免了深度优先搜索可能带来的栈溢出问题。 - 防除零:代码中
mid / n逻辑前,有if (m == 0 || n == 0)的特判。在本生成器中,通过参数控制保证了 。
- 非递归:生成器和解法均采用
-
NOI 规范兼容:
- 输出格式严格遵守“空格分隔,换行结尾”。
- 布尔值按要求输出为
true或false字符串。
如何使用
- 将上述代码保存为
gen.cpp。 - 使用
g++ gen.cpp -o gen -std=c++14编译。 - 运行
./gen,当前目录下会立即生成1.in, 1.out ... 10.in, 10.out共 20 个文件。 - 将这些文件打包上传至你的 OJ 题目后台即可。
-
0
你好!在 NOI 竞赛中,从暴力解法逐步演进到最优解法是训练思维的必经之路。下面我为你准备了三个版本的代码,分别代表了不同的算法深度。
所有的代码均遵循 C++14 标准,并采用 NOI 规范的辅助函数和文件 IO 风格。
版本一:暴力扫描法 (Brute Force)
思路:不利用矩阵的任何有序性质,直接遍历所有元素。 适用场景:数据规模极小(如 ),或者作为对拍(Stress Test)的基准程序。
#include <iostream> #include <cstdio> using namespace std; // 全局变量定义在外面,防止栈溢出 int matrix[105][105]; int main() { // NOI 竞赛通常要求文件读写 // freopen("matrix.in", "r", stdin); // freopen("matrix.out", "w", stdout); int m, n, target; if (!(cin >> m >> n)) return 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { cin >> matrix[i][j]; } } cin >> target; bool found = false; // 暴力:双重循环,时间复杂度 O(m*n) for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (matrix[i][j] == target) { found = true; break; } } if (found) break; } if (found) cout << "true" << endl; else cout << "false" << endl; return 0; }- 时间复杂度:。
- 空间复杂度: 用于存储矩阵。
版本二:逐行二分法 (Row-wise Binary Search)
思路:利用“每行递增”的性质。遍历每一行,在每一行内进行二分查找。 思考过程:如果我们只用性质1,那么每一行都是独立的有序数组。
#include <iostream> #include <vector> using namespace std; int mat[105][105]; bool binarySearch(int row, int n, int target) { int l = 0, r = n - 1; while (l <= r) { int mid = l + (r - l) / 2; // 防止 l+r 过大导致整型溢出 if (mat[row][mid] == target) return true; if (mat[row][mid] < target) l = mid + 1; else r = mid - 1; } return false; } int main() { int m, n, target; cin >> m >> n; for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) cin >> mat[i][j]; cin >> target; bool found = false; for (int i = 0; i < m; i++) { // 优化:只有当 target 可能在当前行的范围内时才进行二分 if (target >= mat[i][0] && target <= mat[i][n-1]) { if (binarySearch(i, n, target)) { found = true; break; } } } cout << (found ? "true" : "false") << endl; return 0; }- 时间复杂度:。
- 优化点:加入了边界判定
target >= mat[i][0],在实际运行中会快很多。
版本三:全局二分映射法 (Optimal - Single Binary Search)
思路:由于“下一行首元素大于上一行末元素”,整个矩阵在逻辑上是一个连续升序序列。 核心点:将二维坐标 映射为一维索引 ,反之亦然。
#include <iostream> using namespace std; const int MAXN = 105; int mat[MAXN][MAXN]; int main() { int m, n, target; // 快速输入处理 if (!(cin >> m >> n)) return 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { cin >> mat[i][j]; } } cin >> target; // 特判:空矩阵直接返回 if (m == 0 || n == 0) { cout << "false" << endl; return 0; } // 将 2D 矩阵视为长度为 m*n 的 1D 有序数组 int left = 0; int right = m * n - 1; bool found = false; while (left <= right) { int mid = left + (right - left) / 2; // 关键点:一维索引转二维下标 // 易错点:这里必须除以列数 n,而不是行数 m int row = mid / n; int col = mid % n; int val = mat[row][col]; if (val == target) { found = true; break; } else if (val < target) { left = mid + 1; } else { right = mid - 1; } } cout << (found ? "true" : "false") << endl; return 0; }
复杂度分析与优化建议
1. 复杂度分析思考过程
- 时间复杂度:
- 从 到 ,最后到 。
- 等价于 。对于 的矩阵,最多只需要比较 次。
- 空间复杂度:
- 除了存储数据的矩阵外,仅使用了常数个变量,即 。
- 在 NOI 中,如果 达到 级别,不能开
int mat[10000][10000],因为会超过 256MB 限制。此时应改用std::vector或动态分配内存。
2. 易错点提醒
- 坐标转换公式:在
row = mid / n中,n必须是列数。很多同学会误写成m。你可以想象:每一行有n个元素,所以跳过多少个n就代表在第几行。 - 二分边界:
right = m * n - 1。注意下标从 0 开始,不要越界。 - 整型溢出:虽然本题 ,但如果 达到 ,
m * n会溢出int。在 NOI 中建议习惯性使用long long处理索引乘积:long long right = 1LL * m * n - 1;。
3. 进阶优化建议 (针对 NOI)
- I/O 优化:如果数据量巨大(例如 个数),请使用
scanf或快读getchar()代替cin,以防超时。 - 单调性变体:如果题目改为“每行递增,每列也递增,但下一行首不一定大于上一行末”(即 LeetCode 240),则二分法失效,需要使用 “Z字形搜索”(又称阶梯搜索),从右上角开始走,时间复杂度 。
总结:读题关键词在哪里?
- “递增/有序”:这是使用二分查找的绝对信号。
- “首位大于末位”:这是告诉你可以把矩阵拉平成一维序列的信号。
- “ 矩阵”:提示你需要进行二维到一维的下标映射。
- 1
信息
- ID
- 19395
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 3
- 已通过
- 1
- 上传者