算法基础-枚举

目录

[1 普通枚举](#1 普通枚举)

[1.1 铺地毯](#1.1 铺地毯)

[1.2 回文日期](#1.2 回文日期)

[1.3 扫雷](#1.3 扫雷)

[2 ⼆进制枚举](#2 ⼆进制枚举)

[2.1 子集](#2.1 子集)

[2.2 费解的开关](#2.2 费解的开关)

[2.3 Even Parity](#2.3 Even Parity)


顾名思义,就是把所有情况全都罗列出来 ,然后找出符合题目要求的那⼀个 。因此,枚举是⼀种纯暴力的算法。

⼀般情况下,枚举策略都是会超时的 。此时要先根据题目的数据范围来判断暴力枚举是否可以通过。如果不行的话,就要用其他算法来进行优化(比如⼆分,双指针,前缀和与差分等)。

使用枚举策略时,重点思考枚举的对象(枚举什么),枚举的顺序(正序还是逆序),以及枚举的⽅式(普通枚举?递归枚举?⼆进制枚举)。(在这篇博客里的枚举题目都是不会超时的,注要是让大家了解枚举,后续的算法基础会跟深层次的学习枚举。)

1 普通枚举

1.1 铺地毯

题⽬来源: 洛⾕

题⽬链接: P1003 [NOIP2011 提⾼组] 铺地毯

难度系数: ★

解法

枚举所以的地毯,找出最后覆盖题目要求点的地毯即可。

策略:

从后往前枚举所有的地毯,第⼀次找到覆盖题目中给的点就是结果;

参考代码

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e4 + 10;
int a[N],b[N],g[N],k[N];
int n;
int x,y;

int find(){
	//从后往前枚举 
	for(int i = n;i >= 1;i--){
		//判断是否覆盖 
		if(a[i] <= x && b[i] <= y && a[i] + g[i] >= x && b[i] + k[i] >= y){
			return i;
		}
	}
	return -1;
}

int main(){
	cin >> n;
	for(int i = 1;i <= n;i++){
		cin >> a[i] >> b[i] >> g[i] >> k[i];
	}
	cin >> x >> y;
	cout << find() << endl;
	return 0;
}
1.2 回文日期

题⽬来源: 洛⾕

题⽬链接:P2010 [NOIP2016 普及组] 回⽂⽇期

难度系数: ★

解法

枚举所有日月的组合,然后根据回文的特性推出年份,然后比较这个数字是否在题目给定的区间内。

策略一:枚举x~y之间所有的数字,然后判断是否回文。如果回文,那就拆分成年月日,判断是否是合法日期即可。(时间复杂度 1e10,符合题目要求的)

策略二:仅需枚举年份,然后拆分成回文形式的月日,判断是否合法即可;(时间复杂度 2e3)

策略三:枚举所有的日和月的组合,然后拼接成相应的年份,判断是否合法。(下面的参考代码就是策略三的解法)

参考代码

cpp 复制代码
#include<iostream>
using namespace std;

int x,y;
int day[] = {0,31,29,31,30,31,30,31,31,30,31,30,31};
int ret;

int main(){
	cin >> x >> y;
	//枚举日月的组合 
	for(int i = 1;i <= 12;i++){
		for(int j = 1;j < day[i];j++){
			int num = j%10*1000 + j/10*100 + i%10*10 + i/10;
			int k = num*10000 + i*100 +j;
			if(k>= x && k<= y) ret++;
		}
	} 
	
	cout << ret << endl;
	return 0;
} 
1.3 扫雷

题⽬来源: 洛⾕

题⽬链接: P2327 [SCOI2005] 扫雷

难度系数: ★★

解法

当第⼀列中,第⼀行的小格子的状态确定了之后,其实后续行的状态也跟着固定下来。而第⼀列中,第⼀行的状态要么有雷,要么没有雷,所以最终的答案就在 0, 1, 2 中。

因此,我们枚举第⼀列中,第⼀行的两种状态:要么有雷,要么没雷。然后依次计算剩下⾏的值,看看是否能满⾜所给的数据。

参考代码

cpp 复制代码
​​#include<iostream>
using namespace std;

const int N = 1e4 + 10;
int a[N],b[N];
int n;

int check1(){
	a[1] = 1;
	for(int i = 2;i <= n+1;i++){
		a[i] = b[i-1] - a[i-2] - a[i-1];
		if(a[i] < 0 || a[i] >1) return 0;
	}
	if(a[n+1] == 0) return 1;
	else return 0;
}
int check2(){
	a[1] = 0;
	for(int i = 2;i <= n+1;i++){
		a[i] = b[i-1] - a[i-2] - a[i-1];
		if(a[i] < 0 || a[i] >1) return 0;
	}
	if(a[n+1] == 0) return 1;
	else return 0;
}
int main(){
	cin >> n;
	for(int i = 1;i <= n;i++) cin >> b[i];
	int ret = 0;
	ret += check1();//a[1]有雷
	ret += check2();//a[1]无雷
	
	cout << ret << endl; 
	return 0;
}

2 ⼆进制枚举

⼆进制枚举:用⼀个数⼆进制表示中的 0/1 表示两种状态,从而达到枚举各种情况。

  • 利⽤⼆进制枚举时,会用到⼀些位运算的知识。
  • 关于用⼆进制中的 0/1 表示状态这种方法,会在动态规划章节中的状态压缩 dp 中继续使用到。
  • ⼆进制枚举的方式也可以用递归实现,后续递归算法中会再讲到
2.1 子集

题⽬来源: ⼒扣

题⽬链接:⼦集

难度系数: ★

解法

利用二进制枚举的方式,把所有情况都枚举出来( 用零表示不选某一位数,用1表示选择某一位数)

1、枚举0~1右移n-1之间所有的数

2、根据枚举的数中二进制表示中1的位置,把相应的数选出来

参考代码

2.2 费解的开关

题⽬来源: 洛⾕

题⽬链接: P10449 费解的开关

难度系数: ★★★

分析题目得出:

1.每一盏灯,最多只会点一次(对于一盏灯而言,只有按或者不按两种状态)

2.按法的先后顺序,是不影响最终结果的(不用关心按的顺序,只用关心按了什么)

3.第一行的按法确定之后,后续灯的按法就跟着确定了

解法

1.暴力枚举第一行的所有按法;

2.根据第一行的按法,计算出当前行以及下一行被按之后的结果;

3.根据第一行的结果,推导出第二行的按法;重复2/3过程

4.直到按到最后一行,然后判断所有灯是否全亮。

如何实现这个解法?

1.用二进制枚举第一行所有的按法,0 ~(1 << 5)-1

2.跟据二进制表示中一共有多少1,计算出一共按了多少次;

3.用二进制表示。来存储灯的初始状态。(存的时候,把0存成1,把1存成0)此时题目就从全亮变成全灭(这样特别好判断终止情况,同时代码更简单)

4.用位运算的知识,快速计算出被按之后的状态

5.根据按法,计算当前行以及下一行被按之后的状态。

参考代码

cpp 复制代码
#include<iostream>
#include<cstring>
using namespace std;

const int N = 10;
int a[N];//二进制表示,来存储灯的状态 
int t[N];//备份a数组 
int n = 5;
//计算x的二进制表示中一共有多少个1 
int calc(int x){
	int cnt  = 0;
	while(x){
		cnt++;
		x &= x-1;
	}
	return cnt;
}
int main(){
	int T;cin >> T;
	while(T--){
		//多组测试时,一定要注意清空之前的数据 
		memset(a,0,sizeof(a));
		for(int i = 0;i < n;i++){
			for(int j = 0;j < n;j++){
				char ch;cin >> ch; 
				//存成相反的
				if(ch == '0') a[i] |= 1 << j;	
			}
		}
		//统计所有按法中的最小值
		int ret = 0x3f3f3f3f;
		//枚举第一行所有的按法
		for(int st = 0;st < (1 << n);st++){
			memcpy(t,a,sizeof(a));
			int push = st;//当前的按法
			int cnt = 0;//统计当前按法下一共按了多少次 
			//依次计算后续行的结果以及按法
			for(int i = 0;i < n;i++){
				
				cnt += calc(push); 
				//修改当前行被按的结果
				t[i] = t[i] ^ push ^ (push << 1) ^ (push >> 1);
				t[i] &= (1 << n) -1;//清空影响
				//修改下一行的状态
				t[i+1] ^= push;
				//下一行的按法
				push = t[i];  
			} 
			if(t[n-1]==0) ret = min(ret,cnt);
		} 
			if(ret > 6) cout << -1 << endl;
			else cout << ret << endl;
		
	}
	return 0;
}
2.3 Even Parity

题⽬来源: 洛⾕

题⽬链接: Even Parity

难度系数: ★★★

分析题目得出:

1.针对每一个0,只有变一次,或者不变两种清况,因此,每一个0都有两种状态

2.当我们决定改变哪些0之后,改变的顺序是不影响最终结果的,我们只需要关心哪些0最终被改变即可。

3,当我们第一行的"最终结果"确定之后,后续行的"最终结果"也跟着确定了

解法

1.暴力枚举第一行的最终状态

2.判断最终状态是否合法;

3.如果合法,推导出下一行的最终状态。重复2、3过程

4.直到推导到最后一行,并判断最后一行是否合法。

如何实现这个解法?

1.直接用二进制枚举第一行有可能得所有最终结果0~(1<<n)-1

2.一层for循环,判断a[i]和最终状态(change)相应的二进制表示中是否合法:

如果是0->1合法,搞一个变量sum记录一下

如果是1->0,不合法,返回一个-1

3.当前行的最终状态a[i]确定之后,如何推导出下一行的最终状态nextchange

参考代码

cpp 复制代码
#include<iostream>
#include<cstring>
using namespace std;

const int N = 20;
int a[N];//用二进制存储状态 
int t[N];//备份 
int n;

//判断x->y是否合法
//返回-1,表示不合法
//其余的数表示合法,并且表示0->1的次数
int calc(int x,int y){
	int sum = 0;
	for(int i= 0;i < n;i++)
	{
		if(((x >> i)&1)==0 && ((y >>i)&1) == 1)sum++;
		if(((x >> i)&1)==1 && ((y >>i)&1) == 0)return -1;
	}
	return sum;
} 
int solve()
{
	int ret = 0x3f3f3f3f;
	//枚举第一行的最终状态 
	for(int st = 0;st < (1 << n);st++)
		{
			memcpy(t,a,sizeof(a));
			int change = st;
			int cnt = 0;//统计0->1的次数 
			bool falg = 1;
			for(int i = 1;i <= n;i++)
			{
				// 先判断change是否合法
				int c = calc(t[i],change);
				if(c == -1)
				{
					falg = 0;
					 break;
				}
				cnt += c;//累加次数 
				//当前行的最终状态
				t[i] = change; 
				change = t[i-1] ^(t[i]<<1)^(t[i]>>1);
				change &= (1 << n)-1;
			}
			if(falg)ret = min(ret,cnt);
		}
		if(ret == 0x3f3f3f3f)return -1;
		else return ret;	
}
int main(){
	int T;cin >> T;
	for(int k = 1;k <= T;k++)
	{
		//多组测试数据,记得清空
		memset(a,0,sizeof(a)); 
		cin >> n;
		//避免越界访问 
		for(int i = 1;i <= n;i++)
		{
			for(int j = 0;j < n;j++)
			{
				int x;cin >> x;
				if(x) a[i] |= 1 << j; 
			}
		}
		printf("Case %d:%d\n",k,solve());
	}
	return 0;
}
相关推荐
小森77671 小时前
(三)机器学习---线性回归及其Python实现
人工智能·python·算法·机器学习·回归·线性回归
振鹏Dong1 小时前
超大规模数据场景(思路)——面试高频算法题目
算法·面试
uhakadotcom1 小时前
Python 与 ClickHouse Connect 集成:基础知识和实践
算法·面试·github
uhakadotcom1 小时前
Python 量化计算入门:基础库和实用案例
后端·算法·面试
uhakadotcom2 小时前
使用 Python 与 BigQuery 进行交互:基础知识与实践
算法·面试
uhakadotcom2 小时前
使用 Hadoop MapReduce 和 Bigtable 进行单词统计
算法·面试·github
XYY3692 小时前
前缀和 一维差分和二维差分 差分&差分矩阵
数据结构·c++·算法·前缀和·差分
longlong int3 小时前
【每日算法】Day 16-1:跳表(Skip List)——Redis有序集合的核心实现原理(C++手写实现)
数据库·c++·redis·算法·缓存
24白菜头3 小时前
C和C++(list)的链表初步
c语言·数据结构·c++·笔记·算法·链表
刺客-Andy3 小时前
前端加密方式 AES对称加密 RSA非对称加密 以及 MD5哈希算法详解
前端·javascript·算法·哈希算法