2022年第十三届蓝桥杯C/C++ B组省赛题解

49b5a1042d764b71bdef29b44e3c732d.jpg

目录

前言

A.九进制转十进制

B.顺子日期

C.刷题统计

D.修剪灌木

E.X 进制减法

F.统计子矩阵

G.积木画

H.扫雷

I.李白打酒加强版

J.砍竹子


前言

相信很多朋友都报名了2023年的蓝桥杯吧,为了准备今年的蓝桥杯,今天带大家回味一下去年的蓝桥杯,文里的题解都是我当时考场代码或者赛后补题代码,可能会有些不合理的地方,欢迎大家提意见!

下面我们开始吧!!


A.九进制转十进制

题目:

9进制整数gif.latex?%282022%29_%7B9%7D转换为10进制等于多少?

输入:

输出:

写个程序直接输出结果即可。

问题解析: 

签到题,考验进制的转换,而且还是转换成较容易转换的十进制。而且数值比较小,可以直接用计算器计算得出结果。

gif.latex?2*%289%29%5E%7B3%7D+0*%289%29%5E%7B2%7D+2*%289%29%5E%7B1%7D+2%3D1478


代码:

注意本题是填空题!代码要求直接输出答案,不要输出多于内容,如下:

#include<cstdio>
int main(){
	printf("1478");
	return 0;
}


B.顺子日期

题目:

小明特别喜欢顺子。顺子指的就是连续的三个数字:123,456等。顺子日期指的就是在日期的 yyyymmdd 表示法中,存在任意连续的三位数是一个顺子的日期。例如 20220123 就是一个顺子日期,因为它出现了一个顺子:123。而 20221023则不是一个顺子日期,它一个顺子也没有。

小明想知道在整个2022年份中,一共有多少个顺子日期。

输入: 

输出: 

写个程序直接输出结果即可。

问题解析:

签到题,但这个题在去年刚参加完的时候有异议,那就是012是不是顺子,我当时比赛的想法是012也是顺子的。题目本身不难,是蓝桥杯喜闻乐见的日历题,可以写代码也可以对着日历看!

因为前四位2022是确定的,而月份没有第34月,所以如果是顺子日期,则必然是后四位中有顺子,只有在第1,10,11,12月的日期可能满足这个条件,所以我们思考这几个月即可。可以对照日历也可以找出顺子日期。

分别为20220120,20220121,20220122,20220123,20220124,20220125,20220126,20220127,20220128,20220129,20221012,20221123,20221230,20221231共14个。


代码:

注意本题是填空题!代码要求直接输出答案,不要输出多于内容,如下:

#include<cstdio>
int main(){
	printf("14");
	return 0;
}


C.刷题统计

题目:

小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做a道题目,周六和周日每天做b道题目。请你帮小明计算,按照计划他将在第几天实现做题数大于等于n题?

输入:

10 20 99

输出:

8

评测用例规模与约定: 

对于50%的评测样例,gif.latex?1%5Cleqslant%20a%2Cb%2Cn%5Cleqslant10%5E%7B6%7D

对于100%的评测样例,gif.latex?1%5Cleqslant%20a%2Cb%2Cn%5Cleqslant%2010%5E%7B18%7D

问题解析:

签到题, 基本没有太多坑的一道题,要注意题目中大于等于,并且评测样例要求10的18次方,需要开longlong


代码:

70%做法(直接暴力):

#include<cstdio>
long long a,b,n,ans,day;
int main(){
	scanf("%lld%lld%lld",&a,&b,&n);
	while(1){
		for(int i=1;i<=7;i++){
			if(i==6||i==7) ans+=b;
			else ans+=a;
			day++;
			if(ans>=n){
				printf("%lld",day);
				return 0;
			}
		}
	}
}

不断循环每一天,根据星期几来加对应的题目,但数据过大时循环的天数可能很多,很容易就会超过规定时间 。

100%做法:

#include<cstdio>
long long a,b,n,ans;
int main(){
	scanf("%lld%lld%lld",&a,&b,&n);
	long long week=n/(5*a+2*b);//先求出需要的整星期数。
	long long day=week*7;
	ans+=week*(5*a+2*b); 
	if(ans>=n) {//如果正好够,直接输出
		printf("%lld",day);
		return 0;
	}
	for(int i=1;i<=7;i++){ //如果不够,再加一个循环。
		if(i==6||i==7) ans+=b;
		else ans+=a;
		day++;
		if(ans>=n) {
			printf("%lld",day);
			return 0;
		}
	}
} 

只需要一点很小的优化就可以省去大部分时间,先求整星期,再求具体天数。


D.修剪灌木

 题目:

爱丽丝要完成一项修剪灌木的工作。

有 N 棵灌木整齐的从左到右排成一排。爱丽丝在每天傍晚会修剪一棵灌木,让灌木的高度变为 0 厘米。爱丽丝修剪灌木的顺序是从最左侧的灌木开始,每天向右修剪一棵灌木。当修剪了最右侧的灌木后,她会调转方向,下一天开始向左修剪灌木。直到修剪了最左的灌木后再次调转方向。然后如此循环往复。

灌木每天从早上到傍晩会长高 1 厘米, 而其余时间不会长高。在第一天的早晨, 所有灌木的高度都是 0 厘米。爱丽丝想知道每棵灌木最高长到多高。

输入:

3

输出:

4

2

4

评测用例规模与约定: 

对于 30% 的数据,N≤10;

对于 100% 的数据,1< N ≤10000。

问题解析:

签到题, 因为这道题的解法比较好想,在这里就不写直接暴力模拟每一天的代码了,直接说优化解法。

首先我们从最容易想的两个点说起,即左右两个端点,很容易想到最左端的灌木最长的时刻,即爱丽丝从最左到最右,再从最右回到最左的那一刻,同理最右端点也是如此。

以此类推,对于中间的点,我们先判断他对于最左或者最右哪个距离更长,因为每天增长一厘米,我们可以抽象理解为爱丽丝走一个距离即增长一厘米,直接用长距离×2即为所求。


代码:

100%做法:

#include<cstdio>
int N,a[10010];
int main(){
	scanf("%d",&N);
	for(int i=1;i<=N;i++){
		if(i-1>=N-i) a[i]=2*(i-1);
		else a[i]=2*(N-i);
	}
	for(int i=1;i<=N;i++) printf("%d\n",a[i]);
	return 0;
}

当然这其实是一个左右对称的过程,只求一半也可以,但求全过程时间也完全不会超。我建议大家在比赛的时候选择更加稳妥的写法。


E.X 进制减法

 题目:

进制规定了数字在数位上逢几进一。

X 进制是一种很神奇的进制,因为其每一数位的进制并不固定!例如说某种 X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则X进制数321转换为十进制数为 65。

现在有两个 X 进制表示的整数 A 和 B,但是其具体每一数位的进制还不确定,只知道 A 和B 是同一进制规则,且每一数位最高为 N进制,最低为二进制。请你算出 A−B的结果最小可能是多少。

请注意,你需要保证 A 和 B 在 X 进制下都是合法的,即每一数位上的数字要小于其进制。

输入:

第一行一个正整数 N,含义如题面所述。

第二行一个正整数 Ma,表示 X 进制数 A 的位数。

第三行 Ma个用空格分开的整数,表示 X 进制数 A 按从高位到低位顺序各个数位上的数字在十进制下的表示。

第四行一个正整数 Mb,表示 X 进制数 B的位数。

第五行 Mb 个用空格分开的整数,表示 X 进制数 B 按从高位到低位顺序各个数位上的数字在十进制下的表示。

请注意,输入中的所有数字都是十进制的。

11
3
10 4 0
3
1 2 0

输出:

输出一行一个整数,表示 X 进制数A−B的结果的最小可能值转换为十进制后再模 1000000007 的结果。

94

样例说明:

 当进制为:最低位 2 进制,第二数位 5 进制,第三数位 11 进制时,减法得到的差最小。此时 A 在十进制下是 108,B 在十进制下是 14,差值是 94。

评测用例规模与约定: 

对于30%的数据,gif.latex?N%5Cleqslant%2010%2CM_%7Ba%7D%2CM_%7Bb%7D%5Cleqslant%208%3B

对于100%的数据,gif.latex?2%5Cleqslant%20N%5Cleqslant%201000%2C1%5Cleqslant%20M_%7Ba%7D%2CM_%7Bb%7D%5Cleqslant%20100000%2CA%5Cgeqslant%20B

问题解析:

去年考场上看到这个题是蒙b的,题是在太长,来回看了好几遍题才看懂题意。现在我们来一点一点分析。

首先我们可以知道的是,题里面给了我们一个定义:X进制数,他的每一位数进制都是不一样的,并且给我们举了一个例子,X进制数321(从低位到高位分别为2,10,8进制)转化为十进制数为65我们先来解释一下这个转换,

3*(10*2)+2*2+1=65

很容易发现,将X进制数转化为十进制数,就是将每一位的数乘以所有低于他数位的数制。

例如3在第三位,转化为十进制数就要将3乘以第一和第二位的数制,即2,10。将每一位数都如此计算,最后相加即为10进制数。

下面用到了贪心思想:欲使A-B最小,只需使得各位数字取得合法范围内的最小进制即可,具体做法就是对A和B中相同数位的数字取maxn,该位的合法最小进制即为max(maxn + 1, 2),因为最小进制不能小于2;而对于X进制的数来说,合法的最大数字是X-1,例如8进制中最大数字是7,二进制中最大数字是1。

样例分析:

最低位 0,0 二进制。

第二位 4,2 五进制。

第三位 10 ,1 十一进制。

结果为:(10*5*2+4*2)-(1*5*2+2*2)=94

思路正确,下面我们用代码实现即可。

代码:

30%做法(直接暴力):

#include<cstdio>
#include<iostream>
using namespace std;
const int mod=1000000007;
int a[100010],b[100010],c[100010];
int main(){
	int N,n,m;
	scanf("%d",&N);
	scanf("%d",&n);
	for(int i=n-1;i>=0;i--) scanf("%d",&a[i]);
	scanf("%d",&m);
	for(int i=m-1;i>=0;i--) scanf("%d",&b[i]);
	long long ans1=0,ans2=0;
	for(int i=max(n,m)-1;i>=0;i--){
		c[i]=max(max(a[i],b[i])+1,2);
	}
	for(int i=max(n,m)-1;i>=0;i--){
		for(int j=0;j<i;j++){
			a[i]=a[i]*c[j];
			b[i]=c[j]*b[i];
		}
		ans1+=a[i];
		ans2+=b[i];
	}
	printf("%lld",(ans1-ans2)%mod);
	return 0;
}

就是对上面思路的直接暴力模拟,这个代码不过多进行解释了,时间复杂度不合理,而且两个数很大,即使开longlong也会爆。

100%做法(累乘,每一步都取mod):

#include<cstdio>
#include<iostream>
using namespace std;
const int mod=1000000007;
int a[100010],b[100010],c[100010];
long long w[100010];
int main(){
	int N,n,m;
	scanf("%d",&N);
	scanf("%d",&n);
	for(int i=n-1;i>=0;i--) scanf("%d",&a[i]);
	scanf("%d",&m);
	for(int i=m-1;i>=0;i--) scanf("%d",&b[i]);
	long long ans1=0,ans2=0;
	for(int i=max(n,m)-1;i>=0;i--) c[i]=max(max(a[i],b[i])+1,2);
	w[0]=1;
	for(int i=1;i<=max(n,m);i++) w[i]=w[i-1]*c[i-1]%mod;
	for(int i=n;i>=0;i--) ans1=(ans1+a[i]*w[i])%mod;
	for(int i=m;i>=0;i--) ans2=(ans2+b[i]*w[i])%mod;
	printf("%lld",(ans1-ans2+mod)%mod); //防溢出
	return 0;
}

通过累乘求出每个数位的来减少时间复杂度。

通过开longlong步步取模来防止溢出,注意最后结果可能为,但根据题意,负数显然没有意义,所以先加mod再对mod取模,就可以防止负数出现。


F.统计子矩阵

 题目:

给定一个 N×M 的矩阵 A,请你统计有多少个子矩阵(最小 1×1,最大 N×M) 满足子矩阵中所有数的和不超过给定的整数 K输入:

第一行包含三个整数 N, M和 K。

之后 N 行每行包含 M 个整数,代表矩阵 A。

 3 4 10

1 2 3 4

5 6 7 8

9 10 11 12

输出:

一个整数代表答案。

19

样例说明:

满足条件的子矩阵一共有 19,包含:

  • 大小为 1×1 的有 10 个。

  • 大小为 1×2 的有 3 个。

  • 大小为 1×3 的有 2 个。

  • 大小为 1×4 的有 1 个。

  • 大小为 2×1 的有 3 个。

评测用例规模与约定: 

对于 30% 的数据,N,M≤20;

对于 70% 的数据,N,M≤100;

对于 100% 的数据,1≤N,M≤500,0≤gif.latex?A_%7Bi%2Cj%7D≤1000,1≤K≤250000000。

问题解析:

题目本身很容易理解。

因为矩阵本身没什么规律,题目本质是要求出所有子矩阵的和进行比较, 运用前缀和的思想。

可套用求子矩阵和,前缀和模板:796. 子矩阵的和 - AcWing题库

 代码:

70%做法(子矩阵的和):

#include<cstdio>
const int N=510;
long long n,m,q;
long long s[N][N];
long long ans;
int main(){
	scanf("%lld%lld%lld",&n,&m,&q);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%lld",&s[i][j]);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
		}
	}
	for(int x1=1;x1<=n;x1++){
		for(int y1=1;y1<=m;y1++){
			for(int x2=x1;x2<=n;x2++){
				for(int y2=y1;y2<=m;y2++){
					if(s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]<=q) ans++;
				}
			}
		}
	}
	printf("%lld",ans);
	return 0;
} 

后面数据过大,四重循环显然会超时。

100%做法(双指针优化):

#include<cstdio>
const int N=510;
long long n,m,q;
long long s[N][N];
long long ans;
int main(){
	scanf("%lld%lld%lld",&n,&m,&q);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%lld",&s[i][j]);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
		}
	}
	for(int l=1;l<=m;l++){ //枚举子矩阵左边界
		for(int r=l;r<=m;r++){ //枚举子矩阵右边界
			for(int i=1,j=1;i<=n;i++){ //i为下边界,j为上边界。
				while(j<=i&&s[i][r]-s[j-1][r]-s[i][l-1]+s[j-1][l-1]>q) j++;//判断下一个
				if(j<=i) ans+=(i-j+1); //表明i到j之间的行都满足条件。
			}
		}
	} 
	printf("%lld",ans);
	return 0;
} 

双指针省去时间复杂度。


G.积木画

 题目:

小明最近迷上了积木画,有这么两种类型的积木,分别为 I 型(大小为 2 个单位面积)和 L 型(大小为 3 个单位面积):

99969f0c00f9ccc0fb511c7a1f088408.jpeg

同时,小明有一块面积大小为 2×N 的画布,画布由 2×N 个 1×1 区域构成。小明需要用以上两种积木将画布拼满,他想知道总共有多少种不同的方式?

积木可以任意旋转,且画布的方向固定。

输入:

输入一个整数 N,表示画布大小。

3

输出:

输出一个整数表示答案。由于答案可能很大,所以输出其对 1000000007 取模后的值。

5

样例说明:

五种情况如下图所示,颜色只是为了标识不同的积木:

da5e594b0c5ce4cd9d004f772a03591f.png

评测用例规模与约定: 

对于所有测试用例,1≤N≤10000000。

问题解析:

状压dp,场上看到这个题目,就想到  ​​​​​​  291. 蒙德里安的梦想 - AcWing题库

这个知名的状态压缩dp题(如果不知道状压dp建议先去了解),而且积木画题目中的画布为2*N,完全可以对积木出现的方式直接进行枚举。

既然是动态规划,其核心就是假设已知其中一个状态,求另一个状态的值。这种又被称为状态转移。

dp[i][j]题目给出的状态转移的合法条件为:前i-1列已摆好,当前摆第i列,第i列是j的所有方案。

dp问题的核心是假设已知其中一个状态求另一个状态的值。这种又被称为状态转移

每当我们需要状态转移时,都要先考虑一个问题:状态转移是否合法

根据题意,合法的意义即摆积木不能重叠

先假设第i列状态如下:

b99a41708010461785712a8fe749beed.png

 当i-1列摆放完之后,我们可以看到在这种情况下,第i列没有被伸出来的积木块覆盖。

如果用0表示没有被覆盖,1表示被覆盖,那么我们可以称这种情况为00

那么,在00的基础上,我们可以在i上进行四种不同方式的积木摆放,如下:

ae3d8f8df3774c9782351c24a0bb257b.png

对应i+1列为00

2a3a90ea512c4ee9a3824b23bb287225.png

对应i+1列状态为10 

17c5f5284fb643c88aefa0375d4779a8.png

对应i+1列状态为01 

85912053b10d4aac90ea1f4a136ace89.png

对应i+1列状态为11

那么我们可以得到一个状态转移结论:
假如j当前为00,那么对应的下一列的状态可以为:00,01,10,11

同理,假如i当前为10,那么对应的下一列的状态可以为01, 11,如下:

c497ea6d89b34cab82923dfddd389314.png

149bd816398247cd852ba6bbcbf0d1c8.png

假如i当前为01,那么对应的下一列的状态可以为10, 11,如下:

 e3c7c6ba9d75493b9ccb8f38c196f132.png

 70dcfd961ebe417f8c8d746764567aba.png

 假如i当前为11,那么下一列的状态只能为00

(因为第i列什么也不能放了,所以第i+1列一定是空的)

此时我们已经判断出了所有的状态转移情况,此时我们建立一个二维数组进行存放

(第一维表示初状态,第二维表示目标状态,1表示可以转化,0表示不能转化)

int g[4][4]={
	{1,1,1,1},
 	{0,0,1,1},
	{0,1,0,1},
	{1,0,0,0}
};

 1c424f6e35a747c994c7562fcb5ecd00.png

知道了状态转移,我们就可以写出dp了

还记得我们的dp[i][j]的定义吗,前i-1列已经摆好,有几种可能,使得第i列情况是j

dp[i][j]中存的数字即为这个可能的事件数,容易知道,j一共只有四种可能(00,01,10,11)我们需建立一个dp[N][4]

100%做法(状压dp):

#include<cstdio>
const int N=1e7+10,mod=1000000007;
int g[4][4]={
	{1,1,1,1},
 	{0,0,1,1},
	{0,1,0,1},
	{1,0,0,0}
};
int dp[N][4];
int main(){
	int n;
	scanf("%d",&n);
	dp[1][0]=1; //初始为“00”状态,且这种状态有唯一一种,所以dp[1][0]=1 
	for(int i=1;i<=n;i++){
		for(int j=0;j<4;j++){
			for(int k=0;k<4;k++){
				dp[i+1][k]=(dp[i+1][k]+dp[i][j]*g[j][k])%mod; 
				//g[j][k]判断能不能从j状态转移到k状态,如果能,就把第i列状态为j的方案数加到第i+1列状态为k的方案集合中去
			}
		}
	}
	printf("%d",dp[n+1][0]);
//由题目可知,最后画布会被填满,也就是说前面全部填满,且在第n+1列状态为00的方案总量
	return 0;
} 

循环j,k来判断 dp[i+1][k]=(dp[i+1][k]+dp[i][j]*g[j][k])%mod;

如果能从j转移到k,就把第i列状态为j的方案数加到第i+1列状态为k的方案集合中去,实现状态转移。


H.扫雷

 题目:

b4323c5add2742398f9f5dacbb8a9373.png

输入:

 c9969ce90bcf4408bb40820f89e0f78a.png

2 1

2 2 4

4 4 2

0 0 5

输出:

输出一个整数表示答案。

2

样例说明:

c88773dafa104938a8c071e18e9528b2.png

评测用例规模与约定:

ec193803493142a5867402930e2e64ea.png

问题解析:

看到这题人麻了

首先看到雷爆炸会带动周围其他雷爆炸,很容易想到要用bfs算法来解决

但x,y的数据范围过大(10^9)用数组肯定是会爆的,只能用哈希。

一说到哈希,很容易想到利用c++自带的mapunordered_map来进行储存,但很遗憾,map的速度太慢,没有办法过掉全部测试点,只能用手写哈希的方式来储存。如果对哈希不了解,可以看一下840. 模拟散列表 - AcWing题库

下面来针对这个题目讲解一下具体的哈希方法:

首先,因为x,y的数据范围一样大,我们可以把x,y映射到一个唯一的哈希值,

即为:x*(1e9+1)+y, 这是一个(1e9+1)进制数,可以把这个哈希值近似看成一个数字 :xy

注意事项:首先哈希表的长度至少为2n,在这里我们开的越大越好,这样可以避免哈希冲突

哈希表的keyvalue,哈希表的value即我们刚刚通关计算得到的数字xy,在获取哈希值之后,我们还要将他储存在哈希表的某一个位置,这个位置即为key,我们将刚刚得到的哈希值对哈希表取模即可得到key。如果发生了冲突,就需要从这一点往后遍历,直到找到第一个空闲的位置

100%做法(哈希,bfs):

#include<cstdio>
#include<queue>
using namespace std;
const int N=50010,X=1e9+1;
const int M=1000005;//哈希表的长度 
int n,m;
long long ans=0;
long long h[M];//哈希表 
bool st[N];//判断这一点是否被访问过 
int id[M];//哈希表中key值所对应的地雷下标 
struct node{
	int x,y,r;
}a[N];//地雷 
long long getvalue(int x,int y){ //得到每个坐标的哈希值 
	return (long long)x*X+y;
}
int find(int x,int y){ //找到该坐标对应的哈希表的key 
	long long v=getvalue(x,y);
	int key=(v%M+M)%M; //映射到哈希表内部 
	while(h[key]!=-1&&h[key]!=v){ //如果为空,直接弹出,如果发生了冲突,就往后遍历,直到不冲突 
		key++;
		if(key==M) key=0; //如果到了最后,就从头继续遍历 
	}
	return key;
}
bool check(int x1,int y1,int r, int x,int y){//判断以x1,y1为圆心,半径为r的圆中是否包含点x,y 
	int l=(x1-x)*(x1-x)+(y1-y)*(y1-y);
	if(l<=r*r) return true;
	else return false;
}
void bfs(int res){ //标准的bfs模板,利用数列 
	queue<int> que;
	que.push(res);
	st[res]=1;
	while(!que.empty()){
		int t=que.front();
		que.pop();
		int x=a[t].x,y=a[t].y,r=a[t].r;
		for(int i=x-r;i<=x+r;i++){ //遍历区域内所有点 
			for(int j=y-r;j<=y+r;j++){
				int key=find(i,j);//找到该点对应的哈希下标 
				if(id[key]&&!st[id[key]]&&check(x,y,r,i,j)){//如果说这一点存在,没有被访问过,且可以被扫到 
					int res1=id[key];
					st[res1]=1;//扫雷 
					que.push(res1);
				}
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	int x,y,r;
	for(int i=0;i<M;i++) h[i]=-1;
	for(int i=1;i<=n;i++){ //地雷 
		scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].r);
		x=a[i].x;y=a[i].y;r=a[i].r;
		int key=find(x,y);
		/*if(h[key]==-1) */h[key]=getvalue(x,y);//如果哈希对应位置为空,则插入。 
		if(!id[key]||a[id[key]].r<r){//如果id数组没有被记录,或者来了一颗同位置且爆炸半径更大的雷 
			id[key]=i;
		}
	}
	for(int i=1;i<=m;i++){ //导弹 
		scanf("%d%d%d",&x,&y,&r);
		for(int i=x-r;i<=x+r;i++){
			for(int j=y-r;j<=y+r;j++){
				int key=find(i,j);
				if(id[key]&&!st[id[key]]&&check(x,y,r,i,j)) bfs(id[key]);
				//有地雷,没有被访问过,能炸到,则开始进行扫雷。 
			}
		}
	}
	for(int i=1;i<=n;i++){
		int key=find(a[i].x,a[i].y);//找到坐标点对应的哈希下标 
		if(id[key]&&st[id[key]]) ans++;//如果这一点有地雷,且被访问过,则被扫到了 
	}
	printf("%lld",ans);
	return 0;
} 

代码中bfs的部分没有做详细解释,可以做一些经典的bfs题学习一下!


I.李白打酒加强版

 题目:

5f94aed5442c4dcdb35199f8277938f4.png

输入:

 b7214ef1481d423db75cb59fe416845b.png

5 10

输出:

输出一个整数表示答案。由于答案可能很大,输出模 1000000007 的结果。

14

样例说明:

如果我们用 0 代表遇到花,1 代表遇到店,14 种顺序如下:

010101101000000
010110010010000
011000110010000
100010110010000
011001000110000
100011000110000
100100010110000
010110100000100
011001001000100
100011001000100
100100011000100
011010000010100
100100100010100
101000001010100

评测用例规模与约定 :

029efbb15ac44f7ab91ba6d7f832cc79.png

 问题解析:

经典李白打酒,很明显的一道dp题。

还记得dp的两大要点吗?状态和转移

状态:题目中有三个状态需要我们注意,分别是店,花,酒,在考虑整体状态的时候需要全部考虑进去,所以我们设dp[i][j][k],表示经过了i个花,j个店,手里还有k个酒的的方案总数。

转移:有两种情况,第一种是上一个经过了花,第二种是上一个经过了店,显然状态转移方程就是:

dp[i][j][k]=dp[i-1][j][k+1]+dp[i][j-1][k/2]

100%做法(dp):

#include<cstdio>
const int N=110;
const int mod=1000000007;
long long dp[N][N][N];
int main(){
	int n,m;
	scanf("%d%d",&m,&n);
	dp[0][0][2]=1;//初始化,李白一开始手里有两斗酒 
	for(int i=0;i<=n;i++){
		for(int j=0;j<=m;j++){
			for(int k=0;k<N;k++){
				if(i>0) dp[i][j][k]=(dp[i][j][k]+dp[i-1][j][k+1])%mod; //上一个是花的方案数 
				if(j>0&&k%2==0) dp[i][j][k]=(dp[i][j][k]+dp[i][j-1][k/2])%mod; // 上一个是店的方案数
				//特别注意判断k%2==0,假如k是奇数,k/2=1,而1*2=2,实际上不能从k=1的情况直接转移到k=3 
			}
		}
	}
	printf("%lld",dp[n-1][m][1]);
	//注意这里不能输出dp[n][m][0],因为dp[n][m][0]不能区分李白最后路过的是花还是店
	//往前推一下,如果最后一步是花,那转移前的方案就是dp[n-1][m][1] 
	return 0;
} 

个人认为比积木画简单


J.砍竹子

 题目:

05dd69a140144eab9f7035c31fa8942f.png

输入:

 8305853c52104f539b0c9766a207d2a8.png

6

2 1 4 2 6 7 

输出:

一个整数表示答案。

5

样例说明:

其中一种方案:

  2 1 4 2 6 7
→ 2 1 4 2 6 2
→ 2 1 4 2 2 2
→ 2 1 1 2 2 2
→ 1 1 1 2 2 2
→ 1 1 1 1 1 1

需要5步完成

评测用例规模与约定:

af3d3943371444c3b97bb566f07073f1.png

问题解析:

首先我们需要关注的是,只能对连续一段高度相同的竹子使用魔法

将这一段高度变为4822524e729b456c88508c8ade15c318.png

我们不禁思考,这样的变化最多能进行几次,经过计算,1e18在经过6次变化后就会变为1,也就是说所有的数最多经历6次变化。

这道题主要考查我们的思维,我们把每个数字,在经历变化,最终变成1的过程,抽象为,比如说1e18,他有6层,每层都有一个数字,逐渐减小,最终为1。

假如我们把所有数字单独隔离开,独自层层减少,可以得到一个总数,但这显然不是我们的答案,因为当一个数字的邻居和他相同时,可以一起减小,省去一次魔法。

那如果我们能找到所有数字及他们每一层的数字,整体进行比较,如果发现有相邻的情况,就让总魔法使用数减一,是不是就可以解出来了呢。

100%做法(思维):

#include<cstdio>
#include<cmath>
#include<iostream>
const int N=200010,M=10;
int n,m;
long long f[N][M];//f[i][j]:记录第i个数在第j层的数是多少 
int main(){
	long long ans=0;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		long long x;
		long long stk[M];//因为最后都为1,所以需要从小往大存,利用栈进行翻转 
		int top=0;
		scanf("%lld",&x);
		while(x>1) {
			stk[++top]=x;
			x=sqrt(x/2+1);
		}
		
		ans+=top;//先求出所有数字单个操作次数的累加 
		m=std::max(m,top); //最大层数 
		for(int j=0,k=top;k;j++,k--){//注意栈先进后出,所以k从top开始遍历 
			f[i][j]=stk[k];
		}
	}
	for(int j=0;j<m;j++){ //遍历每一层 
		for(int i=1;i<n;i++){ //每一层的相邻数字,如果有相等则减1 
			if(f[i][j]&&f[i][j]==f[i-1][j]) ans--;
		}
	}
	printf("%lld\n",ans);
	return 0;
} 

 看到还有线段树,优先队列等做法,可以去学习一下(

最后,祝大家在2023年蓝桥杯中能取得自己满意的成绩!!!

  • 29
    点赞
  • 89
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
蓝桥杯是一个国内著名的计算机比赛,为了帮助参赛者更好地准备和了解比赛的题型,委会会公布历的真题并提供相应的题解。 首先,我们需要了解蓝桥杯是一个综合性的计算机比赛,测试的对象包括计算机基础知识、编程能力以及解决实际问题的能力。 在历的真题中,参赛者将面临不同类型的题目,包括算法设计与优化问题、数据结构算法问题、编程题等。其中针对Python B的题目主要考察的是对Python语言的掌握和应用能力。 题目解答一般会包含以下几个方面的内容: 1. 题目分析与理解:读取题目,理解题目的要求和限制条件。通过仔细分析题目,确定题目的输入与输出,以及问题的核心。 2. 设计解决方案:根据题目要求和限制条件,设计一个合适的解决方案。可以使用合适的算法数据结构来解决问题,并做出相应的性能优化。 3. 编写代码实现:根据设计的方案编写相应的代码实现。需要注意的是,Python语言有其独特的语法和特性,掌握好这些特性可以更好地完成编程任务。 4. 调试与测试:编写完代码后,需要进行调试和测试。通过运行样例输入和输出,检查代码是否符合题目要求,并且没有逻辑上的错误。 5. 总结与优化:在完成题目解答后,可以进行总结和优化。包括分析算法复杂度、代码风格和可读性等方面,以便在比赛中更好地表现。 在准备蓝桥杯时,可以通过阅读历的真题和题解来了解比赛的难度和类型,针对性地进行练习和提高。同时也可以参加相关的培训班和讨论活动,与其他参赛者交流经验和技巧。 总而言之,历蓝桥杯真题的解答对于提高自己的编程能力和应对比赛非常有帮助。通过认真分析和实践,可以更好地理解并掌握Python编程,并在比赛中取得更好的成绩。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值