- 该篇总结了递归枚举的三种较为常见方式(排列型枚举、指数型枚举、组合型枚举),以便区分判别
- 然后提供了两道蓝桥杯中考察递归枚举的题目,虽然近年蓝桥的比赛难度稳步攀升,但搜索枚举始终是重点
- 【AcWing】中相应的练习,下面均有链接提供
排列型枚举
题目链接:·【AcWing】·排列型枚举
输入:
3
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
- 最原始的dfs枚举,将枚举的结果存在a[ ] 数组中
- 对于1~n中,已经枚举到的数不再访问,可用state通过二进制标记
————————————————————————
void dfs_1(int step, int state){ //state二进制标记已经枚举过的点
if(step == n){
for(int i = 0; i < n; ++i){
cout << a[i] << " ";
}
cout << endl;
return;
}
for(int i = 0; i < n; ++i){
if(!(1&(state>>i))){
a[step] = i + 1;
dfs_1(step + 1, state | (1<<i));
a[step] = 0;
}
}
}
指数型枚举
题目链接:·【AcWing】·指数型枚举
输入:
3
输出;
3
2
2 3
1
1 3
1 2
1 2 3
- 注意排列型枚举是随机排列,而指数型枚举是随机选取
- 实现的方法有多种,这里将随机选取看作是1~n的顺序枚举,对于i,每次的枚举有两种方式:选取i、不选取i
- 枚举的过程中依然可以通过二进制位来标记
————————————————————————
void dfs_2(int step, int state){
if(step == n){
for(int i = 0; i < n; ++i){
if(1 & (state >> i))
cout << a[i] << " ";
}
cout << endl;
return;
}
dfs_2(step + 1, state);
a[step] = step + 1;
dfs_2(step + 1, state | (1 << step));
a[step] = 0;
}
组合型枚举
题目链接:·【AcWing】·组合型枚举
- 组合型枚举也称为全排列
输入:
5 3
输出:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
- 注意排列型枚举是对n个数随机排列,组合型枚举是n个数中随机选k个数排列
- 可以在指数型枚举的基础上,增加sum参数记录枚举的个数,当
sum == k
时,dfs结束
————————————————————————
void dfs_3(int step, int sum, int state){
if(sum + n - step < k){
return;
}
if(sum == k){
for(int i = 0; i < n; ++i){
if(1 & (state >> i))
cout << a[i] << " ";
}
cout << endl;
return;
}
dfs_3(step + 1, sum, state);
a[step] = step + 1;
dfs_3(step + 1, sum + 1, state | (1 << step));
a[step] = 0;
}
·【蓝桥】·七段码
题目描述
上图给出了七段码数码管的一个图示,数码管中一共有 7 段可以发光的二极管,分别标记为 a, b, c, d, e, f, g。
小蓝要选择一部分二极管(至少要有一个)发光来表达字符。
在设计字符的表达时,要求所有发光的二极管是连成一片的。
例如:b 发光,其他二极管不发光可以用来表达一种字符。
例如:c 发光,其他二极管不发光可以用来表达一种字符。
这种方案与上一行的方案可以用来表示不同的字符,尽管看上去比较相似。
例如:a, b, c, d, e 发光,f, g 不发光可以用来表达一种字符。
例如:b, f 发光,其他二极管不发光则不能用来表达一种字符,因为发光的二极管没有连成一片。
请问,小蓝可以用七段码数码管表达多少种不同的字符?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。
本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
【答案】80
- 指数型枚举
- 七段码即对应7个数,从七个数中选取任意多个,判断是否相连通,最后计算连通的数量
- 对于7个数我是这样记录的,于是就有了初始化
void line(int x, int y){ //标记为1即两个数相连接
a[x][y] = 1;
}
void init(){
line(1, 2); line(2, 1);
line(1, 3); line(3, 1);
line(2, 4); line(4, 2);
line(3, 4); line(4, 3);
line(2, 5); line(5, 2);
line(3, 6); line(6, 3);
line(4, 5); line(5, 4);
line(4, 6); line(6, 4);
line(5, 7); line(7, 5);
line(6, 7); line(7, 6);
}
- 愉快的指数型枚举1~7后,得到的state状态可转换为字符串s(不足7位的首位补零即可)
- 例如得到一组
s = "1010101"
后,如何判断相连呢,再来一遍dfs吧! - 从第一个标记
s[i] == '1'
处出发,遍历所以相连通的数,然后标记为0,最后若字符串s被清零了,则这一组s是连通的
————————————完整代码————————————
#include <iostream>
using namespace std;
int a[8][8];
string s;
int ans = 0;
int n = 7;
void line(int x, int y){
a[x][y] = 1;
}
void init(){
line(1, 2); line(2, 1);
line(1, 3); line(3, 1);
line(2, 4); line(4, 2);
line(3, 4); line(4, 3);
line(2, 5); line(5, 2);
line(3, 6); line(6, 3);
line(4, 5); line(5, 4);
line(4, 6); line(6, 4);
line(5, 7); line(7, 5);
line(6, 7); line(7, 6);
}
string toStr(int n){ //state转为7位的字符串s
s = "";
while(n > 0){
if(n % 2 == 0){
s = "0" + s;
}else{
s = "1" + s;
}
n /= 2;
}
while(s.length() > 0 && s.length() < 7){ //s不足7位的前面添0
s = "0" + s;
}
return s;
}
void dfs_1(int k){ //从某个被标记的点出发,dfs所以相连的、标记过的点
s[k] = '0';
for(int i = 0; i < n; ++i){
if(s[i]=='1' && a[k+1][i+1] == 1){
dfs_1(i);
}
}
}
bool judge(){ //true则连通,false则不连通
if(s.size() == 0){
return false;
}
for(int i = 0; i < s.length(); ++i){
if(s[i] == '1'){
dfs_1(i);
break;
}
}
return s.find('1') == string::npos;
}
void dfs(int k, int state){
if(k == n){
// for(int i = 0; i < 7; ++i){
// if(1 & (state >> i)){
// cout << i + 1 << " ";
// }
// }
s = toStr(state);
//cout << s << endl;
if(judge()){
ans++;
}
return;
}
dfs(k + 1, state);
dfs(k + 1, state | (1 << k));
}
int main(){
init();
dfs(0, 0);
cout << ans << endl;
return 0;
}
·【蓝桥】·剪邮票
题目描述
如【图1.jpg】, 有12张连在一起的12生肖的邮票。
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连)
比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。
请你计算,一共有多少种不同的剪取方法。
【答案】116
- 组合型枚举
- 12个数中随机选取5个数排列,然后判断是否相连通,最后计算连通的数量
- 是不是和上面的七段码很像?只是上题是指数型枚举,这里成了组合型枚举
- 另外不同的是,连通性的判断,较为容易的方法应该是直接借助下标进行dfs,上、下、左、右四个方向,分别对应下标 -4、+4、-1、+1,最后注意边界的判别就可以了
————————————完整代码————————————
#include <cstdio>
#include <iostream>
#include <string>
using namespace std;
int n = 12, m = 5;
int ans = 0;
int a[20];
string s;
void dfs_1(int t){
s[t-1] = '0';
if(t - 4 > 0 && s[t-3] == '1'){ //上
dfs_1(t - 4);
}
if(t % 4 != 0 && s[t] =='1'){ //右
dfs_1(t + 1);
}
if(t + 4 <= 12 && s[t+3] == '1'){ //下
dfs_1(t + 4);
}
if(t % 4 != 1 && s[t-2] == '1'){ //左
dfs_1(t - 1);
}
return;
}
string toStr(int state){
s = "";
while(state > 0){
if(state % 2 == 0)
s = "0" + s;
else
s = "1" + s;
state /= 2;
}
while(s.length() > 0 && s.length() < n){
s = "0" + s;
}
//cout << s << endl;
return s;
}
void dfs(int k, int sum, int state){
if(n + sum - k < m){
return;
}
if(sum == m){
// for(int i = 0; i < n; ++i){
// if(1 & (state >> i)){
// printf("%d ", a[i]);
// }
// }
// printf("\n");
s = toStr(state);
//cout << s << " ";
for(int i = 0; i < n; ++i){
if(s[i] == '1'){
dfs_1(i + 1);
break;
}
}
//cout << s << endl;
if(s.find('1') == string::npos){
ans++;
}
return;
}
dfs(k + 1, sum, state);
a[k] = k + 1;
dfs(k + 1, sum + 1, state | (1 << k));
a[k] = 0;
}
int main(){
dfs(0, 0, 0);
cout << ans << endl;
return 0;
}