枚举:一个不漏地试¶
什么是枚举?¶
枚举(穷举)就是把所有可能一个不漏地试一遍,符合条件的留下。
听起来很笨?但它有两大优点:思路零门槛(会循环就会枚举)、正确性有保障(只要列举完整就不会错)。CSP-J 第二轮前两题往往枚举就能拿分——先拿分,再谈优雅。
枚举的三个要领¶
- 选对枚举对象:枚举什么最省事?直接枚举答案本身,通常比绕弯快
- 范围不重不漏:范围写漏答案丢分,范围写重复浪费时间
- 能省则省:用题目条件缩小范围、跳过不可能的情况(这叫剪枝)
例题 1:水仙花数¶
题目描述 水仙花数:一个三位数,它的每个位上的数字的 3 次幂之和等于它本身(如 \(153 = 1^3 + 5^3 + 3^3\))。输出所有水仙花数。
输入格式 无。
输出格式 若干行,每行一个水仙花数,从小到大。
数据范围 水仙花数是三位数,\(100 \sim 999\)。
思路分析 枚举对象就是答案本身:把 100~999 全部试一遍。三位数拆位用第 2 章的口诀——个位 %10、十位 /10%10、百位 /100。
参考代码
#include <iostream>
using namespace std;
int main() {
for (int n = 100; n <= 999; n++) {
int a = n / 100; // 百位
int b = n / 10 % 10; // 十位
int c = n % 10; // 个位
if (a*a*a + b*b*b + c*c*c == n) {
cout << n << endl;
}
}
return 0;
}
输出 153 370 371 407 四个数。900 次循环瞬间跑完——枚举不怕多,就怕漏。
例题 2:百钱买百鸡¶
题目描述 公鸡 5 元一只,母鸡 3 元一只,小鸡 1 元三只。用 100 元正好买 100 只鸡,公鸡、母鸡、小鸡各买几只?输出所有方案(公鸡数从小到大)。
输入格式 无。
输出格式 每行三个整数:公鸡、母鸡、小鸡的数量。
数据范围 经典题(源自《张邱建算经》)。
思路分析 三个未知数,枚举公鸡 \(a\) 和母鸡 \(b\) 两层循环,小鸡 \(c = 100 - a - b\) 直接算出来——枚举变量能少一个是一个!然后验证两个条件:钱数恰好 100、小鸡数是 3 的倍数(否则没法按「1 元三只」卖)。
参考代码
#include <iostream>
using namespace std;
int main() {
for (int a = 0; a <= 20; a++) { // 公鸡最多 100/5 = 20 只
for (int b = 0; b <= 33; b++) { // 母鸡最多 100/3 = 33 只
int c = 100 - a - b; // 小鸡:凑够 100 只
if (c % 3 != 0) continue; // 小鸡必须按 3 只一组
if (a * 5 + b * 3 + c / 3 == 100) {
cout << a << " " << b << " " << c << endl;
}
}
}
return 0;
}
输出四种方案,如 0 25 75、4 18 78。注意两个细节:
- 循环上限别写 100:公鸡不可能超过 20 只——按预算上限剪枝
continue把「小鸡不成组」的情况提前跳过,主条件更清爽
枚举的效率账¶
| 数据规模 | 枚举量 | 能否接受 |
|---|---|---|
| \(10^6\) | 100 万 | ✅ 秒过 |
| \(10^8\) | 1 亿 | ⚠️ 1 秒左右,临界 |
| \(10^{10}\) | 100 亿 | ❌ 必超时 |
做题前先估一估枚举量。超了怎么办?缩小范围(剪枝)、减少枚举维度(像百钱买百鸡那样用公式消元)、或者换算法(后面的贪心、二分都是出路)。
动手试一试¶
- 枚举所有四叶玫瑰数:四位数,各位数字的 4 次幂之和等于本身
- 读入 \(n\)(\(n \le 10^4\)),问 1~n 中有多少个数能被 3 整除但不能被 5 整除(想想:这题需要枚举吗?有没有 \(O(1)\) 公式?——枚举是兜底,动脑有惊喜)
- 完美立方:读入 \(a\)(\(2 \le a \le 100\)),输出所有满足 \(a^3 = b^3 + c^3 + d^3\)(\(b \le c \le d \le a\))的写法
- 挑战:三次循环版百钱买百鸡和两层版,循环次数差多少倍?体会「消去一个维度」的价值
下一课:把题目逐句翻译成代码——模拟。