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

2022年第十三届蓝桥杯C/C++ B组省赛题解_第1张图片

目录

前言

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
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
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
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
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
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
#include
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

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

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

#include
#include
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
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
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 个单位面积):

2022年第十三届蓝桥杯C/C++ B组省赛题解_第2张图片

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

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

输入:

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

3

输出:

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

5

样例说明:

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

2022年第十三届蓝桥杯C/C++ B组省赛题解_第3张图片

评测用例规模与约定: 

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

问题解析:

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

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

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

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

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

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

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

先假设第i列状态如下:

2022年第十三届蓝桥杯C/C++ B组省赛题解_第4张图片

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

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

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

2022年第十三届蓝桥杯C/C++ B组省赛题解_第5张图片

对应i+1列为00

2022年第十三届蓝桥杯C/C++ B组省赛题解_第6张图片

对应i+1列状态为10 

2022年第十三届蓝桥杯C/C++ B组省赛题解_第7张图片

对应i+1列状态为01 

2022年第十三届蓝桥杯C/C++ B组省赛题解_第8张图片

对应i+1列状态为11

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

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

2022年第十三届蓝桥杯C/C++ B组省赛题解_第9张图片

2022年第十三届蓝桥杯C/C++ B组省赛题解_第10张图片

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

 2022年第十三届蓝桥杯C/C++ B组省赛题解_第11张图片

 2022年第十三届蓝桥杯C/C++ B组省赛题解_第12张图片

 假如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}
};

 2022年第十三届蓝桥杯C/C++ B组省赛题解_第13张图片

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

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

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

100%做法(状压dp):

#include
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.扫雷

 题目:

2022年第十三届蓝桥杯C/C++ B组省赛题解_第14张图片

输入:

 2022年第十三届蓝桥杯C/C++ B组省赛题解_第15张图片

2 1

2 2 4

4 4 2

0 0 5

输出:

输出一个整数表示答案。

2

样例说明:

2022年第十三届蓝桥杯C/C++ B组省赛题解_第16张图片

评测用例规模与约定:

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
#include
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 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

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


I.李白打酒加强版

 题目:

2022年第十三届蓝桥杯C/C++ B组省赛题解_第17张图片

输入:

 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
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;k0) 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.砍竹子

 题目:

2022年第十三届蓝桥杯C/C++ B组省赛题解_第18张图片

输入:

 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
#include
#include
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;i1) {
			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

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

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

你可能感兴趣的:(蓝桥杯,c++,算法,数据结构,c语言)