【蓝桥杯AcWing】专题———递归枚举(重点的重点)

  • 该篇总结了递归枚举的三种较为常见方式(排列型枚举、指数型枚举、组合型枚举),以便区分判别
  • 然后提供了两道蓝桥杯中考察递归枚举的题目,虽然近年蓝桥的比赛难度稳步攀升,但搜索枚举始终是重点
  • 【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】中,粉红色所示部分就是合格的剪取。
请你计算,一共有多少种不同的剪取方法。
1.jpg
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值