CC++ & Algorithm

你还在用“聪明办法”解鸡兔同笼?有时候“笨”才是真智慧

2026年9月6日关联知识点:C++枚举算法14 次阅读

你有没有过这种时刻——明明一道题想破了脑袋要找最优解,结果旁边一个人慢悠悠地说:“直接全部试一遍不就行了?”

然后你愣住了,对啊,为什么不呢?

在编程的世界里,这种“全部试一遍”的策略有个正式的名字:枚举。很多初学者对它嗤之以鼻,觉得这是“笨办法”,不够优雅。但我想告诉你一个反直觉的事实:在C++三级考试的战场上,枚举往往是你最可靠的朋友,没有之一。

枚举不是“傻”,而是“全”

先看一个生活场景。你面前有一个三位数的密码锁,密码忘了。聪明人可能会分析制造商的习惯、按键磨损程度……但最朴素也最有效的办法是什么?从000试到999。最多1000次,你一定能打开。这就是枚举。

它看起来“笨”,但它的核心优势是确定性——只要枚举范围没有遗漏,答案就必然在其中。这是一种“以空间换安心”的策略,在竞赛中,能拿到的分数才是硬道理。

三个问题,想清楚就成功了一半

别急着写代码。写枚举代码之前,先逼自己回答三个问题:

  1. 枚举什么?(对象是数字、下标,还是组合?)
  2. 范围多大?(从哪到哪?边界含不含?)
  3. 满足什么条件?(用哪个if来筛选?)

就拿经典的“水仙花数”来说:一个三位数,各位数字的立方和等于它本身。

  • 枚举对象:所有的三位数,即 num
  • 范围:100 到 999
  • 条件:百位立方 + 十位立方 + 个位立方 == num

你看,思路一旦清晰,代码就是水到渠成的事。核心就这几行:

for (int num = 100; num <= 999; num++) {
    int a = num / 100;          // 百位
    int b = (num / 10) % 10;    // 十位
    int c = num % 10;           // 个位
    if (a*a*a + b*b*b + c*c*c == num) {
        cout << num << " ";
    }
}

一道题告诉你:枚举的“范围”决定你的成败

我见过太多学生,题目分析得头头是道,一写代码就翻车。翻车点往往不是逻辑,而是边界。

看这道题:给定三角形两条边的长度 a 和 b(正整数),第三条边也是正整数,问能组成多少种不同的三角形?

考察点:这题表面考三角形,实际上考的是枚举范围的定义。你首先得知道三角形三边关系的定理:两边之和大于第三边,两边之差小于第三边。

那么第三条边 c 的范围是什么?不是 1 到无穷大,而是 abs(a - b) < c < a + b。

为什么? 因为 c 必须大于两边之差(否则短边加第三条边够不到长边),同时必须小于两边之和(否则两条短边合起来也够不到第三条边)。

所以,枚举范围就是 c 从 abs(a - b) + 1 到 a + b - 1。这里如果范围想错了,哪怕你判断条件写得再对,答案也是错的。这就是枚举的残酷之处——范围错,全盘输。

嵌套枚举:当一层循环不够用

有时候,一层循环解决不了问题,比如“鸡兔同笼”。你有头数 head 和脚数 foot,问鸡和兔各几只?

如果只枚举鸡的数量 chicken,那么兔子数量 rabbit 就不是被枚举的,而是被计算出来的:rabbit = head - chicken。然后你只需要判断脚数是否匹配。

for (int chicken = 0; chicken <= head; chicken++) {
    int rabbit = head - chicken;
    if (chicken * 2 + rabbit * 4 == foot) {
        cout << "鸡:" << chicken << ",兔:" << rabbit;
        break; // 找到一组解,跳出
    }
}

这里有个小技巧:如果你能通过数学关系减少一个枚举变量,就不要傻傻地用两层循环。 这不仅是效率问题,更是思维深度的体现。

但有些问题无法避免嵌套,比如“奇怪的车牌号”那道题:车牌是8位数,前4位是递增自然数,后4位也是递增自然数,且所有数字之和是某个整数的平方。

分析:前4位递增,最多只能是 0123 到 6789。后4位同理。如果你枚举整个8位数,从00000000到99999999,那就是一亿次循环,虽然理论上能跑完,但效率极低。聪明的做法是分别枚举前4位和后4位,再组合判断。这就把一亿次降到了几千次。

这道题的精髓在于:枚举的对象不是“整个车牌”,而是“前四位”和“后四位”这两个独立的部分。 这告诉我们,枚举对象选得好,效率翻倍;选得差,程序跑到天荒地老。

一个容易忽视的坑:完全数的“因子范围”

来看“完全数”——一个数的所有真因子(除了它本身)之和等于它自己。比如 6 = 1 + 2 + 3。

很多新手会这样写内层循环:

for (int factor = 1; factor < num; factor++) {
    if (num % factor == 0) sum += factor;
}

这没错,但效率低。你只需要枚举到 num / 2 就够了——因为一个数除了它自己,最大的因子不可能超过它的一半。

for (int factor = 1; factor <= num / 2; factor++) {
    if (num % factor == 0) sum_factor += factor;
}

这就是枚举的优化思想:在不漏掉答案的前提下,尽可能缩小范围。 考试中不会让你枚举到一亿,但如果你能主动缩小范围,这本身就是一种能力体现。

最后,聊聊枚举的本质

很多人觉得枚举是“没办法的办法”。但我想说,枚举是算法思维的基石。你想想,动态规划的本质是什么?是枚举所有状态并记录最优值。搜索的本质是什么?是枚举所有路径并剪枝。你连“把所有情况列出来”都做不到,又怎么谈优化?

所以,别急着追求“巧办法”。先把枚举练到炉火纯青——边界不丢、条件不漏、范围精准。当你发现枚举太慢的时候,你自然就有动力去学那些更“快”的算法了。但在此之前,枚举是你最坚实的后盾。

考试时,遇到不会的题,先问自己一句:我能枚举什么? 很多时候,答案就藏在问题里。


关于作者

我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。

这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。

如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)

这篇文章对你有帮助吗?

有用 100%没用 0%
点赞的会员
评论0

还没有评论,来抢沙发~

评论加载中...

想系统学习这个知识点?查看完整知识点 →