











NOIP 1997 普及组第一题(洛谷 P2241 数据加强版),主要考察组合计数原理、循环枚举与数学规律推导。本题既可以通过双重循环枚举矩形的长宽直观求解,也可以通过组合数学与求和公式实现 $\mathcal{O}(\min(n, m))$ 乃至 $\mathcal{O}(1)$ 的高效求解。同时本题也是经典的“防溢出(long long)”教学题。GESP 三、四级以上可练习。题目难度⭐⭐☆☆☆,洛谷难度等级普及−。
1997 年普及组第一题
有一个 $n \times m$ 方格的棋盘,求其方格包含多少正方形、长方形(不包含正方形)。
一行,两个正整数 $n,m$($n \leq 5000,m \leq 5000$)。
一行,两个正整数,分别表示方格包含多少正方形、长方形(不包含正方形)。
这道题目是一道经典的平面几何计数问题。我们需要在 $n \times m$ 个小方格组成的网格中分别统计出:
在几何学中,正方形是特殊的长方形(矩形)。因此: \(\text{所有矩形总数(包含正方形)} = \text{正方形个数} + \text{纯长方形个数}\)
由此可得: \(\text{纯长方形个数} = \text{所有矩形总数} - \text{正方形个数}\)
只要我们分别求出 正方形个数 与 矩形总数,即可直接相减得出答案。
一个 $n \times m$ 的方格网格由 $(n+1)$ 条横向网格线和 $(m+1)$ 条纵向网格线构成:
根据乘法原理,网格中包含的所有矩形总数为: \(\text{Total} = \binom{n+1}{2} \times \binom{m+1}{2} = \frac{n(n+1)}{2} \times \frac{m(m+1)}{2}\)
正方形要求长和宽相等。设正方形的边长为 $k$,显然 $k$ 的取值范围为 $1 \le k \le \min(n, m)$。
将正方形计数式展开: \(S = \sum_{k=1}^K (n - k + 1)(m - k + 1) \quad (\text{其中 } K = \min(n, m))\) 设 $A = n + 1, B = m + 1$,则: \((A - k)(B - k) = AB - (A + B)k + k^2\) 代入数列求和公式 $\sum_{k=1}^K k = \frac{K(K+1)}{2}$ 与平方和公式 $\sum_{k=1}^K k^2 = \frac{K(K+1)(2K+1)}{6}$,即可直接在 $\mathcal{O}(1)$ 复杂度内求出 $S$!
long long)int 的最大值约为 $2.14 \times 10^9$。int 范围,若使用 int 存储或计算,会发生整型溢出导致结果为负数或错误答案。long long 类型!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
// 提高输入输出效率
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long n, m;
if (!(cin >> n >> m)) {
return 0;
}
// 1. 利用组合公式计算所有矩形(包含正方形)的总数
long long total_rectangles = (n * (n + 1) / 2) * (m * (m + 1) / 2);
// 2. 单层循环枚举正方形的边长 k
long long squares = 0;
long long limit = min(n, m);
for (long long k = 1; k <= limit; ++k) {
squares += (n - k + 1) * (m - k + 1);
}
// 3. 纯长方形数量 = 总矩形数 - 正方形数
long long pure_rectangles = total_rectangles - squares;
cout << squares << " " << pure_rectangles << "\n";
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long n, m;
cin >> n >> m;
long long squares = 0; // 正方形数量
long long pure_rectangles = 0; // 不含正方形的长方形数量
// 枚举矩形的长 i 和宽 j
for (long long i = 1; i <= n; ++i) {
for (long long j = 1; j <= m; ++j) {
long long count = (n - i + 1) * (m - j + 1);
if (i == j) {
squares += count;
} else {
pure_rectangles += count;
}
}
}
cout << squares << " " << pure_rectangles << "\n";
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long n, m;
cin >> n >> m;
// 总矩形数
long long total_rectangles = (n * (n + 1) / 2) * (m * (m + 1) / 2);
// 正方形总数公式展开计算
long long K = min(n, m);
long long A = n + 1;
long long B = m + 1;
// sum_1 = sum(k, 1..K) = K*(K+1)/2
// sum_2 = sum(k^2, 1..K) = K*(K+1)*(2*K+1)/6
long long sum_1 = K * (K + 1) / 2;
long long sum_2 = K * (K + 1) * (2 * K + 1) / 6;
long long squares = K * A * B - (A + B) * sum_1 + sum_2;
long long pure_rectangles = total_rectangles - squares;
cout << squares << " " << pure_rectangles << "\n";
return 0;
}
| 方法 | 时间复杂度 | 空间复杂度 | 优缺点与适用场景 |
|---|---|---|---|
| 双重循环枚举 | $\mathcal{O}(n \times m)$ | $\mathcal{O}(1)$ | 逻辑直观、易理解,适用于初学者理解几何分割 |
| 单层循环边长 | $\mathcal{O}(\min(n, m))$ | $\mathcal{O}(1)$ | 最推荐,计算量仅 $\le 5000$,兼顾了简洁性与运行速度 |
| 纯数学公式 | $\mathcal{O}(1)$ | $\mathcal{O}(1)$ | 速度极限(常数时间),适用于 $n, m \le 10^9$ 的超大规模数据 |
知识点拓展总结:
long long 的良好习惯。所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code
GESP 学习专题站:GESP WIKI
"luogu-"系列题目可在洛谷题库进行在线评测。
"bcqm-"系列题目可在编程启蒙题库进行在线评测。
欢迎加入:Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答
欢迎加入:C++ GESP/CSP认证学习QQ频道,考试资源总结汇总
欢迎加入:C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。