枚举入门:一个一个试,找出符合条件的答案¶
什么时候用?¶
如果候选答案不多,可以全部试一遍:检查每一个,把符合条件的留下来。这种方法叫“枚举”。
先会:for 循环、条件判断。这里的“枚举”是解题方法,不是 C++ 的 enum 类型。
写循环之前,回答三个问题¶
- 枚举谁?例如整数
x、购买数量a、位置i。 - 从哪里到哪里?必须保证答案不会漏掉。
- 怎样判断符合要求?把题目条件变成
if。
最后再想:要输出所有答案、数答案个数,还是找最好的一个?
例一:找出 1 到 n 中能被 3 整除的数¶
约定 1 <= n <= 1000。
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int count = 0;
for (int x = 1; x <= n; x++) {
if (x % 3 == 0) {
cout << x << ' ';
count++;
}
}
cout << '\n' << count << '\n';
return 0;
}
输入 10,第一行输出 3 6 9(末尾有空格),第二行输出 3。输入 2,第一行为空行,第二行是 0。
每个候选数恰好检查一次,所以不会重复;范围从 1 到 n,所以不会漏掉。这份代码同时展示“列出”和“计数”,做自己的题时只保留题目要求的输出。
例二:两种笔,正好花完 n 元¶
一种笔 3 元一支,另一种 5 元一支。每种可以买 0 支,正好花完 n 元,有多少种购买方案?同一种笔的购买顺序不区分。
约定 0 <= n <= 1000。设两种笔各买 a、b 支,条件就是 3 * a + 5 * b == n。
两层枚举:先把思路写清楚¶
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int count = 0;
for (int a = 0; a <= n / 3; a++) {
for (int b = 0; b <= n / 5; b++) {
if (3 * a + 5 * b == n) {
count++;
}
}
}
cout << count << '\n';
return 0;
}
输入 15,输出 2,方案是 (a, b) = (0, 3) 和 (5, 0)。输入 1 输出 0。
为什么从 0 开始?因为某种笔可以不买。为什么最多 n / 3 支?再多一支,光第一种笔就超预算了。
少试一些:枚举 a,算出 b¶
已知买了 a 支第一种笔,剩余 rest = n - 3 * a 元。只要剩余钱能被 5 整除,就恰好对应一种非负整数 b。
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int count = 0;
for (int a = 0; a <= n / 3; a++) {
int rest = n - 3 * a;
if (rest % 5 == 0) {
count++;
}
}
cout << count << '\n';
return 0;
}
本例允许 n 为 0,此时“不买任何笔”是一种方案,答案是 1。如果题目要求至少买一支,就要额外排除它。
两层写法的检查次数最多约为 (n / 3 + 1) × (n / 5 + 1),随 n 的平方增长,写作 O(n²);一层写法是 O(n)。两种方法都正确,但少试一些会更快。
容易出错的地方¶
- 漏端点:题目包括 R,就不能写成
x < R。 - 重复计数:选两个人组队时,
(1, 2)和(2, 1)通常是同一队,可以令第二个编号从i + 1开始。 - 遗漏零:允许“不选”“不买”时,要考虑从 0 开始。
- 找到一个就停止:要统计所有方案时,不能找到第一种就
break。 - 范围太大:十亿个候选数并不适合逐个检查。先估计循环次数,不存在“所有枚举题都能过”的模板。
自己练习¶
把例一改为统计 [1, n] 内“能被 3 整除,但不能被 2 整除”的数。n = 10 时有哪些?
提示与答案
条件是 x % 3 == 0 && x % 2 != 0。符合条件的是 3、9,共 2 个。