蓝桥杯 第十六届(2025)真题思路复盘解析

发布于:2025-07-09 ⋅ 阅读:(17) ⋅ 点赞:(0)

本文以洛谷平台所提供的题目描述及评测数据为基础进行讲解。


前言:这是本人的蓝桥杯试卷,大概排省一前40%的位置,实际上这届题目偏难,我没有做出太多的有效得分。我把当时的思路和现在学习的思路都复盘进来,希望给大家做个参考。

T1(5分)

题面描述

解析

根据题意,我们很快可以画出左边这张图,这是一条可行的路径。显然是半径r+弧长l

现在这个题面说“交替,不限次数”的使用两种操作,那么这样一下来可能把很多人又给吓迷糊了。但是如果我们画画图,按照右边的图去分析,我们会发现:不论怎么走,路径长度是一样的!这其实就有点考平面几何的意思,你把水平路径平移到x轴,圆弧路径往右平移,这本质仍然是r+l。

第一个理论性的难题就过了。剩下的问题是,怎么求半径和弧长?
半径显然用勾股定理,弧长我们要知道 l=r*θ,需要求这个角度。
考试的时候我发<cmath>没有arctan这个函数,所以没办法,我自己手搓了一个二分法求arctan的值。回来我才知道求角度的函数是atan。遇事不决的时候,构造二分解方程是个不错的选择。

但是主播还是出错了,原因在于:我把x和y看反了!我求的是半径加蓝色的弧长。。。怒亏五分

Code

#include <bits/stdc++.h>
#include <cmath>
#define eps 1e-6 
using namespace std;
double x=233.0,y=666.0;
double l,r,sita;
double arctan(double y,double x); 
int main() {
	r=sqrt(x*x+y*y);
	sita=arctan(y,x);
	//cout<<r<<' '<<sita<<endl;
	l=r*sita;
	cout<<(long long)(l+r)<<endl;
	return 0; 
}
double arctan(double y,double x){
	double t1=0,t2=3.15/2,mid=(t1+t2)/2;
	while(fabs(t1-t2)>=eps){
		if(tan(mid)-y/x>eps){
			t2=mid;
		}
		else if(tan(mid)-y/x<-eps){
			t1=mid;
		}
		else{
			break;
		}
		mid=(t1+t2)/2;		
		//cout<<t1<<' '<<t2<<' '<<tan(mid)<<' '<<y/x<<endl;
	}
	return mid;
}

T2(5分)

这题主播也是没想明白,所以照搬(翻译)的这个讲解:

【蓝桥杯】第十六届C++B组省赛真题详解_哔哩哔哩_bilibili

题面描述

解析

第一个条件就是说结果这是一个排序

第二个看起来很炸裂很复杂,但是实际上我们只要抓住两条信息:

1.i可以等于j,那么先令i=j试试会怎么样。测试代码在第一段。

可以发现(不过确实很难往这方面想),1到1013的排列可以是乱的(A1012<=1013,A1013<=1013),从1014开始,Ai必须等于i本身。

2.既然i>=1014时,Ai等于i,那么令i<=1013,j>=1014,可以得到Ai*j<=i*j+2025,这个对于任意的j都成立。

对于任意的j都成立,而2025/1014和2025/2025向下取整都是1,那么Ai<=i+1。

也就是说,A1=1 2,A2=1 2 3,A3 = 1 2 3 4......但是数不能重复选择。特殊的,A1013最高取1013,不能取1014,因为1014已经被A1014选走了。

所以实际上这是个选择的问题,从A1开始选才不会乱,一直是C_2^1乘下去,到最后是2^1012(而不是2^1013,因为上面解释了,第1013个数实际上没得选)

Code

#include <iostream>
#include <cmath>
using namespace std;
long long n=2025;
int main() {
	for(int i=1;i<=n;++i){
		cout<<i<<' '<<(int)sqrt(i*i+2025)<<endl;
	}
	return 0;
}
#include <iostream>
#define M 1000000007
using namespace std;
long long n=1012,res=1;
int main() {
	for(int i=1;i<=1012;++i){
		res=res*2%M;
	}
	cout<<res<<endl;
	return 0;
}

T3(10分)

题面描述

解析

这题我喜提0分,我在考试的时候是分奇偶讨论的,好像是奇数还是偶数我推出来了全成立,剩下的有条件。

但是,这题比我想象的还更暴力无解:既然序列中的数字是连续递增的整数,还可包括负数,那么我们构造的序列把前面和后面抵消掉,只剩下这个数字本身,就可以了,抵消的办法就是把负数拉过来。比如3,我们构造-2 -1 0 1 2 3,-2到2抵消掉,只剩下3本身。

所以,对于大于1的数都成立,只有1不成立。

Code

注意,<bits/stdc++.h>不能用count,这本身是一个函数。

#include <iostream>
using namespace std;
long long n,x,count=0;
int main() {
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>x;
		if(x!=1)
			++count;
	}
	cout<<count;
	return 0;
}

T4(10分)

题面描述

解析

这其实就是一道很纯粹的模拟题。
如果你做的是a=(b+c)/2;b=(a+c)/2;c=(a+b)/2;那么回家吧孩子,回家吧。值覆盖都没搞懂啊这是。

但是没完,注意到k<=10^9,说明你的时间很可能会超。那就需要一个优化,手动推导就可以发现,a=b=c的时候直接break掉,可以省很多时间。

但是洛谷的数据莫名其妙有点抽风,,,见下,错掉的全部是RE错误。我把if(a==b&&b==c)改成if(a==b&&b==c&&a==c)就过了。

Code

#include <iostream>
using namespace std;
long long T=0,k=0,a=0,b=0,c=0;
int main() {
	cin>>T;
	for(int i=1;i<=T;++i){
		cin>>a>>b>>c>>k;
		while(k!=0){
            if(a==b&&b==c&&a==c)
	            break;
	        long long ta=0,tb=0,tc=0;
			ta=(b+c)/2;
			tb=(a+c)/2;
			tc=(a+b)/2;
			a=ta;
			b=tb;
			c=tc;
            --k;
		}
		cout<<a<<' '<<b<<' '<<c<<endl;
	}
	return 0;
}

T5(15分)

题面描述

解析

这题开始上难度了,但是想要分析出来也并不难。

使得L达到最小值,那么有一个基本的出发点就是贪心。因为所有数字都是正的,那么后面的大于前面的,形成一个单调序列,才能够让L取最小。所以先把数组设为a,进行sort排序。

然后我们来计算每个a[i]^2-a[i-1]^2的值。构成一个数组b[i]。那么这题转化为求b数组里面一个长度为m-1的子序列,使得和达到最小值。为什么是m-1,因为你选M个数,b数组只能得出m-1个值。那么显然有暴力做法骗分。

但是,都分析到这一步子序列了,你就不能想着优化一下吗!这个就是典型的前缀和的问题,我们构造一个前缀和数组c[i],那么求c[i+m-1]-c[i]的min就可以了,时间复杂度哐哐降!

Code

#include <iostream>
#include <algorithm>
using namespace std;
long long n,m;
long long a[100005]={0},b[100005]={0},c[100005]={0};
int main() {
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		cin>>a[i];
	}
	sort(a+1,a+n+1);
	for(int i=2;i<=n;++i){
		b[i]=a[i]*a[i]-a[i-1]*a[i-1];
		c[i]=c[i-1]+b[i];
	}
	long long ans=c[m]-c[1];
	for(int i=1;i<=n-m+1;++i){
		//cout<<i+m-1<<' '<<i<<' '<<c[i+m-1]<<' '<<c[i]<<endl; 
		if(c[i+m-1]-c[i]<ans)
			ans=c[i+m-1]-c[i];
	}
	cout<<ans;
	return 0;
}
/*可以拿下面的做测试,输出应该为0。
6 2
1 2 3 4 4 5
*/

T6(15分)

略有遗憾的一题。

题面描述

解析

这是一篇非常有个人特色的题解。这是我省赛打的最后一道题,一开始用图的dfsbfs我发现不给力,直接进行数学推导可能还会好一些,所以这篇题解倾向于数学和模拟。当时没有完全正确,现在重新写了一遍,知道了问题所在。

首先基础的也是main函数里面的:把字符串翻译成图,用数组s去存储。
除了数组s,我还开了一组数组a,这个是用来寻找'#'开头和'#'结尾的。
我的基本思路是,要是碰见(1)..##..(2)..#.#.这样的,我们直接定位到第三列和第五列,只要在中间去讨论要不要补,而前面后面都是空白,那就没有讨论的必要。
所以我的bgn表示最开头的#,fnl表示最尾巴的#,中间用一个循环,从bgn到fnl,从左向右检测。

这个“检测”的具体操作如下:

我们知道,一列分为四种情况:
(1)# (2). (3)# (4).
(1)# (2). (3).  (4)#

我先进行"记忆化",定义一个lastpos,分别表示一些情况,你可以理解为方向:
lastpos=3,前面是(1),lastpos=2,前面是(4),lastpos=1,前面是(3),至于两个点,不定义。

1.如果这一列都是#,也就是都是检测器,那么ans就不用++了,"方向"改成3

2.如果这一列都是. ,也就是啥都没有,那么ans必须++,然后"方向"不需要改变(在这里引入lastpos,也是为了防止中间有很多的. ,因为如果只看前一列,你是判断不出前面的情况的)

3.如果这一列是情况(3),那么此时要讨论前面的"方向"是什么:
如果lastpos=3,那么ans不需要++,你的lastpos=1,因为这一列的状态不变。
如果lastpos=2,也就是上一个和这一个完全相反
也就是 . # 或者 . . . . #这种中间全是点的(中间的已经被判断出来并且ans++了,故等效于左边)
           # .          #. . . .
那么加一个检测器到右下角等效于这种情况:. #,现在你的lastpos就是3了,因为这列填满了。
                                                                       # #
如果lastpos=1,那前面的情况和现在的情况是一样的,那就不用动了,ans不用加,方向不用改。

4.如果这一列是情况(4),同理,你根据逻辑的对称性也能推理出来。

那么现在我来说说我错的点:lastpos=3和lastpos=2当时没有讨论清楚,我只有lastpos=1的讨论
我给一个测试点:

..#.#..
.#.#.#.

如果只有lastpos=1,那么我需要补充四个点(除了最后一列都得补充)。但是实际只要补充两个点。
因为我没意识到,补充了点之后,这一列状态就相当于lastpos=3了,也就是万能墙了。
第二个错的点就是,测试点全为...的时候要特判,不然我的逻辑答案是1(bgn=fnl,然后调到第二种情况ans++),洛谷会喜提19/20。

Code

#include<iostream>
#include<cmath>
using namespace std;
long long n;
string str1,str2;
char ch;
bool s[3][1000005]={0};
long long a[3][1000005]={0},topA=0,topB=0;
long long lastpos=0;
long long ans=0;
void fun();
int main()
{
	cin>>str1>>str2;
	n=str1.length();
	
	for(int i=1;i<=n;i++){
		ch=str1[i-1];
		if(ch=='#'){
			a[1][++topA]=i;
			s[1][i]=1;
		}
	}
	for(int i=1;i<=n;i++){
		ch=str2[i-1];
		if(ch=='#'){
			a[2][++topB]=i;
			s[2][i]=1;
		}
	}
	
	fun();
	cout<<ans;
	return 0;
}

void fun()
{
	int bgn=min(a[1][1],a[2][1]),fnl=max(a[1][topA],a[2][topB]);
	//cout<<bgn<<' '<<fnl<<endl;
	if(topA==0&&topB==0) return;
	
	for(int i=bgn;i<=fnl;i++){
		if(s[1][i]==1&&s[2][i]==1){
			lastpos=3;
		}
		else if(s[1][i]==0&&s[2][i]==0){
			ans++;
		}
		else if(s[1][i]==1&&s[2][i]==0){
			if(lastpos==2){
				ans++;
				lastpos=3;
			}
			else{
				lastpos=1;
			}
		}
		else if(s[1][i]==0&&s[2][i]==1){
			if(lastpos==1){
				ans++;
				lastpos=3;
			}
			else{
				lastpos=2;
			}
		}
	}
	return ;
}

T7(20分)

题面描述

分析

我是对照着这篇来学习理解的

题解:P12136 [蓝桥杯 2025 省 B] 生产车间 - 洛谷专栏

差不多关于这题的精神思想理解到位了,但是这一段的代码实现我始终啃不下来,可能是背包题本身还不熟练吧。

这是我理解到的图。这题的意思就是列举每个点位所有的可能性。

T8(20分)

题面描述

解析

对于这题,我大概听到9分钟,就关掉视频了,悟道了。大家也可以试试看能听到多久你就明白了。

本题只有三种运算:加法、减法、异或,而异或等级最高。这个实际上是反逻辑的,计算机是先加减后异或。所以我在考试的时候搞了模拟,开一个数栈和一个符号栈去暴力,代码特别长3^13次方也约等于10^6,只能骗前30%的点(不过6分也很香)。

那么本题的正解是什么呢?不可能是死算,那么我们能不能从样例着手分析?

聪明的你从样例其实就应该能发现了!对于一个空格,可以填+也可以填-,总有一个对应的方式,让他们加起来和=0,只留下前面的数据。

所以,我们实际上留下来的是什么?

这个规律用我就不用文字表述啦(表达能力有限咳咳)

那么代码怎么实现方便呢?每次都进行异或运算,显然不合适。那么我们开一个数组a,a[i]=a[i-1]^输入的数,就不用每次都来计算了,就很方便!

然后由于pow可能过大,要考虑模,而且每次都计算模幂非常麻烦,所以我们自己写一个数组b存储模幂运算结果。

Code

#include<iostream>
#define M 1000000007
using namespace std;
long long n,x,res=0; 
long long a[100010]={0};
long long b[100010]={0}; 
void fun();
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>x;
		a[i]=a[i-1]^x;
	}

	b[0]=1;
	b[1]=3;
	for(int i=2;i<=n;i++){
		b[i]=(b[i-1]*3)%M;
	}
	
	for(int i=1;i<=n-1;i++){
		res=(res+a[i]*2*b[n-1-i])%M;
	}
	res=(res+a[n])%M;
	cout<<res<<endl;
	return 0;
}

综上,蓝桥杯不愧是数学杯。。。


网站公告

今日签到

点亮在社区的每一天
去签到