目录
内容简介
备赛蓝桥杯c++组期间,大致总结初阶的一些基础入门算法。在每个篇章里面我会写一些重点题目和做题技巧。
1.模拟
模拟题目算是蓝桥杯里的签到题,一般题目会给出具体的操作,我们只需要按照题目给的内容模拟出操作即可。容易出错的点在于代码实现中的情况判断,在复杂的模拟题中,我们很容易遗漏某种情况导致出错。
基于此类的题目,一般会给出一个矩阵,让我们按题目规律在矩阵中填数。针对此类题目,我们可以使用通解。
1.定义四个方向变量,对应上下左右方向。
2.根据题目要求,通过加上方向变量的值,控制下一个填数方向。
3.在一个方向填数的时候,走到最后会出现越界的情况,当判断走到越界的时候,我们需要更新方向变量,控制下一个新的填数方向。
#include <iostream>
using namespace std;
const int N=10;
int n;
int arr[N][N];
//方向变量:右下左上
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int main(){
cin>>n;
int x=1,y=1;//记录当前填入位置
int cnt=1;//记录当前填入的数
int pos=0;//记录当前方向
while(cnt<=n*n){
//在当前位置填数
arr[x][y]=cnt;
//计算下一个位置坐标
int a=x+dx[pos],b=y+dy[pos];
//判断下一个位置是否合法
if(b>n||a>n||b<1||arr[a][b]!=0)//越界
{
pos=(pos+1)%4;//更新方向
a=x+dx[pos],b=y+dy[pos];//计算正确位置
}
x=a,y=b;//更新位置
cnt++;//更新填数
}
//打印
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
printf("%3d",arr[i][j]);
}
cout<<endl;
}
}
模拟题的重点就是细化情况,一定需要条理逻辑清晰,考虑细节,分类讨论。
2.高精度
在蓝桥杯比赛中,几乎每题都会给出数据的范围,有部分题目给出的数据范围超过了C++数据的最大范围,所有的类型都不够存储数据,此时我们需要用到高精度算法来计算,本质也是模拟算法。
1.用字符串读入数据,再把数据的每一位逆序存放到数组
2.利用数组,进行模拟运算
我们用高进度算法进行模拟加减乘除运算的时候,基本的注意点就是:逆序存放,求当前位的计算结果,处理余数,处理进位,处理结果前导零。
在高精度减法中,我们需要注意让绝对值较大的数字减去绝对值较小的数字,因为我们使用字符串读入数字,默认采用字典序的排序方式来判断大小。例如:99和100,程序会认为99>100,因为第一位9>1。
所以我们在字典序判断之前可以比较两个字符串的长度,字符串长度较长的数会较大。如果字符串长度相等,我们再采用字典序判断,这样就不会出现误判。
在高进度乘法中,我们在计算当前位之后不用着急处理进位,而是先放置不动,继续计算下一位的值。等所有对应位置都乘完,并且累加完毕。我们再去统一处理进位。
3.枚举
枚举,顾名思义就是把所有的情况都列举出来,再根据题目判断是否符合要求,是一种暴力求解的方法。所以,在着手写代码之前,我们需要大致判断一下时间复杂度,如果超时,我们则需要采用其他更优的算法。但是枚举也分三六九等,好的枚举可以大幅减少枚举次数,减少超时的情况。
3.1 普通枚举
P1003 [NOIP 2011 提高组] 铺地毯 - 洛谷
题目要求我们求出指定坐标最上面的毯子编号。我们可以运用枚举,列出所有毯子,判断是否可以覆盖到(x,y)坐标,最后覆盖到(x,y)坐标的毯子编号就是答案。
优化:这题的枚举虽然不会超时,但是我们需要从第一张毛毯枚举到最后一张,才可以得到最上面的毛毯编号,有没有什么办法可以优化呢?
当然是可以的,我们可以从从后往前枚举,这样我们就可以先得到最上面的毛毯编号。当我们枚举的时候,判断当前毛毯是否覆盖(x,y),如果覆盖就直接输出编号即可,不需要全部枚举。
P2010 [NOIP 2016 普及组] 回文日期 - 洛谷
更典型的可以展现枚举优化的题目
1.我们可以枚举年份,根据回文特性推出月日,再判断是否合法。
2.我们可以枚举日月,根据回文特性推出年份,再判断是否合法。
那此时我们选择策略1还是策略2,我们可以简单算一下时间复杂度,策略1的时间复杂度是On,n最大是9999,策略2的时间复杂度是O,具体为月份12×天数30=360。此时,策略2是更优的解法
3.2二进制枚举
二进制枚举中,我们需要掌握位运算的知识,之后才可以更好的运用并理解。
我举一个很简单的例子,商店有四种价格不一样水果,我去商店买水果每种水果最多买一个,也可以不买水果,不限制种类,我有多少种选法,怎么枚举出不同的选法。
每种水果都有两种状态,选法应该是种,此时我们就可以用到二进制枚举出不同的选法。
#include <iostream>
using namespace std;
string fruit[4]={"苹果","香蕉","橙子","桃子"};
int ret;
int main()
{
for(int st=0;st<(1<<4);st++){
ret++;
if(st==0) {
cout<<"不选"<<endl;
continue;
}
for(int i=0;i<4;i++){
if((st>>i)&1) cout<<fruit[i]<<" ";
}
cout<<endl;
}
cout<<"选法总数为"<<ret<<endl;
return 0;
}
第一个for循环,列举了0000,0001,0010,0011……1110,1111等16个数字,第二层for循环,通过(st>>i)&1,就可以得到当前位是否需要选中。例如,st=1001。(st>>0)&1得到1,说明选中苹果;(st>>1)&1得到0,说明不选香蕉;(st>>2)&1得到0,说明不选橙子;(st>>3)&1得到1,说明选中桃子。
这是很简单的一道模板题,与选水果的方法是一样的,可以用来练手,加强对位运算的理解。
当然如果掌握的不错了,可以来试一下这题。难度会更大一点,需要考虑的细节会更多。
4.前缀和
前缀和就是求“前i位”的和,例如arr[5]={1,2,3,4,5}; 前缀和数组f[5]={1,3,6,10,15};相信这个例子是很好理解的,利用代码实现就是循环实现f[i]=f[i]+f[i-1];那我们为什么要建立前缀和数组呢?其实是为了快速解决求[l,r]区间和的问题,我们通过一道题更好的去理解一下。
在学习前缀和之前,我们大概率会采用枚举的方法,把所有字段和都枚举出来,找到值最大的字段和。很明显我们需要两层循环来遍历,在本题是会超时的,此时采用前缀和,我们就可以在求[left,right]字段和的时候,通过f[right]-f[left-1]得到。