本文以洛谷平台所提供的题目描述及评测数据为基础进行讲解。
前言:这是本人的蓝桥杯试卷,大概排省一前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开始选才不会乱,一直是乘下去,到最后是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;
}
综上,蓝桥杯不愧是数学杯。。。