目录
[1. 枚举](#1. 枚举)
[2. 解空间](#2. 解空间)
[3. 循环枚举解空间](#3. 循环枚举解空间)
[4. 例题](#4. 例题)
在算法竞赛(如蓝桥杯)中,有一种最基础、最直观,但也极为实用的算法思想,那就是"枚举"。无论是刚刚入门的初学者还是经验丰富的算法高手,枚举都是必须熟练掌握的核心内功。今天我们就来深入探讨一下枚举的概念,以及它在蓝桥杯实战中的具体应用。
1. 枚举
枚举(Enumeration),在很多场合也被称为"穷举"或"暴力搜索"。它的核心逻辑非常直白:将问题可能出现的所有答案一一列举出来,然后根据题目给定的限制条件逐个进行检验。如果某个候选答案满足所有的条件,那么它就是我们要寻找的正确解。
枚举算法最大的优点在于逻辑清晰、实现简单且不易出错,只要时间允许,它能绝对保证找到正确的答案。在蓝桥杯比赛中,许多难度较低的题目完全可以直接通过枚举拿到满分;而在面对极其复杂的难题时,枚举往往也是一种非常有效的"保底"得分手段(即俗称的暴力部分分)。
2. 解空间
在动手编写枚举代码之前,我们必须先理清一个关键概念:解空间。
解空间指的是一个问题所有潜在可能解的集合。通俗来讲,就是正确答案可能隐藏的那个"大池子"。比如,题目要求寻找一个1到100之间的奇数,那么解空间就是1到100这100个整数。
精准地确定解空间是使用枚举算法的第一步,也是决定成败的一步。只有明确了解空间的范围和边界,我们才能评估需要枚举的次数,进而判断程序能否在比赛规定的时间限制内(通常是1秒钟左右)运行完毕。如果发现解空间过于庞大,我们可能就需要考虑剪枝优化或者直接寻找其他更高级的算法。
3. 循环枚举解空间
明确了解空间的范围之后,接下来的任务就是用代码将它完整地遍历一遍。在绝大多数编程语言中,我们通常通过"循环"结构来实现对解空间的枚举。
最常用的循环语句是 for 循环和 while 循环。对于一维的解空间(例如在一个简单数组中寻找目标值),一层 for 循环通常就足够了;对于二维或者多维的解空间(例如在棋盘或二维网格中寻找特定的图案组合),我们可能需要嵌套多层 for 循环。在循环体的内部,我们会配合 if 条件判断语句,来筛选出真正符合题目要求的那个解。
4. 例题
下面我们结合三道蓝桥杯的经典真题,来看看枚举算法在实际编程中是如何大显身手的。
4.1特别数的和
思路解析: 这道题要求我们在1到n的范围内,找出所有各位数字中含有2、0、1、9这四个数字中任意一个的数,并计算它们的总和。这里的解空间非常明确,就是从1到n的所有整数。我们只需要用一层 for 循环枚举1到n的每一个数,再编写一个函数依次提取出该数字的每一位,检查是否含有这四个特定数字即可。
#include <bits/stdc++.h>
using namespace std;
bool f(int i)
{
while(i)
{
int x = i % 10;
if(x == 2 || x == 0 || x == 1 || x == 9) return true;
i /= 10;
}
return false;
}
int main()
{
int n;
cin >> n;
int ans = 0;
for(int i = 1; i <= n; i++)
{
if(f(i)) ans += i;
}
cout << ans << '\n';
return 0;
}
4.2反倍数
思路解析: 题目要求在1到n的范围内,统计既不是a的倍数,也不是b的倍数,更不是c的倍数的数字的个数。解空间依然是1到n。我们直接用一个 for 循环遍历每一个数字,然后在循环内部利用取余操作符(%)来判断当前数字是否不能被a、b、c整除。如果条件成立,就将答案计数器加一。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, a, b, c;
cin >> n;
cin >> a >> b >> c;
int ans = 0;
for(int i = 1; i <= n; i++)
{
if(i % a != 0 && i % b != 0 && i % c != 0) ans++;
}
cout << ans << '\n';
return 0;
}
4.3找到最多的数
https://www.lanqiao.cn/problems/3227/learning/?page=1&first_category_id=1&problem_id=3227
思路解析: 这道题要求找出一个在矩阵中出现次数超过总元素数量一半的数字。解空间是矩阵中出现过的所有数字。我们可以巧妙地利用 C++ STL 中的 map 数据结构来记录每个数字出现的次数。这其实也是一种变相的枚举:先枚举输入的数据并完成统计,随后再枚举 map 里面存储的所有键值对,找出出现次数超过 n*m 一半的那个目标数字。
#include <bits/stdc++.h>
using namespace std;
map<int, int> mp;
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
for(int i = 1; i <= n * m; i++)
{
int x; cin >> x;
mp[x]++;
}
for(const auto &[x, y] : mp)
{
if(2 * y > n * m) cout << x;
}
return 0;
}
本章完。