第八届蓝桥杯(软件类)决赛C++A组真题题解
A组真题
题目结构
题目 | 类型 | 分值 |
---|---|---|
第一题 | 结果填空 | 17分 |
第二题 | 结果填空 | 43分 |
第三题 | 代码填空 | 25分 |
第四题 | 程序设计 | 41分 |
第五题 | 程序设计 | 75分 |
第六题 | 程序设计 | 99分 |
第一题 平方十位数
-
问题重现
由0~9这10个数字不重复、不遗漏,可以组成很多10位数字。
这其中也有很多恰好是平方数(是某个数的平方)。
比如:1026753849,就是其中最小的一个平方数。
请你找出其中最大的一个平方数是多少?输出
输出一个整数表示答案
-
解题思路
对于这种不能剪枝且数据量较小的直接暴力全排列。
-
代码
/**
*@filename:平方十位数
*@author: pursuit
*@CSDNBlog:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-04-02 10:04
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
//暴力全排列获取所有情况。
ll a[]={1,0,2,3,4,5,6,7,8,9};//由于首位不能为0,所以我们这里字典序初始为1023456789.
double pow_value[]={1e9,1e8,1e7,1e6,1e5,1e4,1e3,1e2,1e1,1e0};
void solve(){
ll maxx=0;
do{
ll ans=0;
for(int i=0;i<10;i++){
ans=ans+ll(a[i]*pow_value[i]);
}
ll temp=sqrt(ans);
if(temp*temp==ans){
maxx=max(maxx,ans);
}
}while(next_permutation(a,a+10));//9814072356
cout<<maxx<<endl;
}
int main(){
solve();
return 0;
}
-
答案
9814072356 9814072356 9814072356。
第二题 生命游戏
-
问题重现
康威生命游戏是英国数学家约翰·何顿·康威在1970年发明的细胞自动机。
这个游戏在一个无限大的2D网格上进行。初始时,每个小方格中居住着一个活着或死了的细胞。
下一时刻每个细胞的状态都由它周围八个格子的细胞状态决定。
具体来说:1.当前细胞为存活状态时,当周围低于2个(不包含2个)存活细胞时, 该细胞变成死亡状态。(模拟生命数量稀少)
2.当前细胞为存活状态时,当周围有2个或3个存活细胞时, 该细胞保持原样。
3.当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成死亡状态。(模拟生命数量过多)
4.当前细胞为死亡状态时,当周围有3个存活细胞时,该细胞变成存活状态。 (模拟繁殖)
例如假设初始是:(X代表活细胞,.代表死细胞) ..... ..... .XXX. ..... 下一代会变为: ..... ..X.. ..X.. ..X.. ..... 康威生命游戏中会出现一些有趣的模式。例如稳定不变的模式: .... .XX. .XX. .... 还有会循环的模式: ...... ...... ...... .XX... .XX... .XX... .XX... .X.... .XX... ...XX. -> ....X. -> ...XX. ...XX. ...XX. ...XX. ...... ...... ...... 本题中我们要讨论的是一个非常特殊的模式,被称作"Gosper glider gun": ...................................... .........................X............ .......................X.X............ .............XX......XX............XX. ............X...X....XX............XX. .XX........X.....X...XX............... .XX........X...X.XX....X.X............ ...........X.....X.......X............ ............X...X..................... .............XX....................... ...................................... 假设以上初始状态是第0代,请问第1000000000(十亿)代一共有多少活着的细胞?
注意:我们假定细胞机在无限的2D网格上推演,并非只有题目中画出的那点空间。
当然,对于遥远的位置,其初始状态一概为死细胞。输出
输出一个整数表示答案
-
解题思路
==这道题说实话在赛场上应该是做不出来的。==其实处理这个演变过程特别简单,对每一个点模拟判断即可,而题目要求的是 10 10 10亿代,我们肯定不是直接模拟 10 10 10亿次,这时间空间根本不够,所以这种题应该就是有规律的,我们可以利用将 100 100 100以内的数据读入文本中,如下:然后利用 E x c e l Excel Excel分析,很难发现,其实每隔 30 30 30代细胞数就增加 5 5 5(除了初始的前 30 30 30代)。注意,由于这里地图没有扩大,导致后面的数据不正确,如果将地图放大,是满足这个规律的。
那么我们实际上就是输出第 n n%30 n代 + + + n / 30 × 5 n/30\times 5 n/30×5。
-
代码
/**
*@fimame:生命游戏
*@author: pursuit
*@CSDNBlog:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-04-02 10:36
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100 + 5;
const int mod = 1e9+7;
//数据规模非常庞大,给出这道题其实就是让我们找规律判断。我们可以先打表0~100代的所有情况。
int go[][2]={{0,1},{0,-1},{1,0},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1}};//移动方向。
//初始地图。根据题意我们去模拟。总共有39行。
string s[]={
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
"...................................X.....................",
".................................X.X.....................",
".......................XX......XX............XX..........",
"......................X...X....XX............XX..........",
"...........XX........X.....X...XX........................",
"...........XX........X...X.XX....X.X.....................",
".....................X.....X.......X.....................",
"......................X...X..............................",
".......................XX................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
".........................................................",
};
string pre_s[29];//用于更新。
void solve(){
int t=0;
int n=29,m=s[0].size();
ofstream outFile;
outFile.open("data.txt");
while(t<=100){
//outFile<<"*************"<<t<<"generation";
int sum=0;//统计有多少细胞。
for(int i=0;i<n;i++){
pre_s[i]=s[i];
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(s[i][j]=='X'){
sum++;
}
}
}
//outFile<<"*************total:"<<sum<<"cells"<<"*************\n";
outFile<<sum;
t%10?outFile<<" ":outFile<<endl;
//接下来开始繁殖,即对每一个都进行判断统计周围的细胞数。
t++;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
sum=0;
//往周围的八个格子统计。
for(int k=0;k<8;k++){
int tx=i+go[k][0],ty=j+go[k][1];
if(tx<0||tx>=n||ty<0||ty>=m)continue;
if(pre_s[tx][ty]=='X'){
sum++;
}
}
//统计好了就开始根据实际情况做出改变。
if(pre_s[i][j]=='X'){
if(sum>3||sum<2){
//大于3个就变成死亡。
s[i][j]='.';
}
}
else{
if(sum==3){
//周围有3个,则存活
s[i][j]='X';
}
}
}
}
}
}
int main(){
solve();//得到的结果就是每循环30代,就增加5个存活的,所以我们。
//先确定余数根据余数确定七点。
ll n=1e9;
cout<<n%30<<endl;//余数为10,即第十代的初始数量,为48
cout<<48+n/30*5<<endl;//166666713
return 0;
}
-
答案
166666713 166666713 166666713。
第三题 表达式计算
-
问题重现
虽然我们学了许久的程序设计,但对于简单的四则混合运算式,
如果让我们完全白手起家地编程来解析,还是有点棘手。
这里,我们简化一下问题,假设只有加法和乘法,并且没有括号来改变优先级。
再假设参加运算的都是正整数。
在这么多的限制条件下,表达式的解析似乎简单了许多。
下面的代码解决了这个问题。请仔细阅读源码,并填写划线部分缺少的代码。
#include <stdio.h> int f3(const char* s, int begin, int end) { int sum = 0; int i; for(i=begin; i<end; i++){ if(s[i]==' ') continue; sum = sum * 10 + (s[i]-'0'); } return sum; } int f2(const char* s, int begin, int end) { int p = begin; int pro = 1; while(1){ int p0 = p; while(p!=end && s[p]!='*') p++; pro *= _______________________________; //填空 if(p==end) break; p++; } printf("f2: pro=%d\n", pro); return pro; } int f(const char* s) { int p = 0; int sum = 0; while(1){ int p0 = p; while(s[p]!=0 && s[p]!='+') p++; sum += f2(s,p0,p); if(s[p]==0) break; p++; } return sum; } int main() { int x = f("12+18+5*4*3+10"); printf("%d\n", x); return 0; }
注意:只填写划线处缺少的内容,不要填写已有的代码或符号,也不要填写任何解释说明文字等。
-
解题思路
不难发现, f f f函数就是实现表达式计算的函数,其中通过 w h i l e while while循环找到+号,将 + + +号之前的数值计算出来。而计算数值则是通过 f 2 f2 f2,对于 f 2 f2 f2首先是判断是否有乘号,也就是说我们需要找到 ∗ * ∗号,然后将 ∗ * ∗号之前的计算出来相乘即可。题中代码思路十分清晰,易得。
-
答案
f3(s,p0,p)
第四题 填字母游戏
-
问题重现
小明经常玩 LOL 游戏上瘾,一次他想挑战K大师,不料K大师说:
“我们先来玩个空格填字母的游戏,要是你不能赢我,就再别玩LOL了”。
K大师在纸上画了一行n个格子,要小明和他交替往其中填入字母。
并且:
\1. 轮到某人填的时候,只能在某个空格中填入L或O
\2. 谁先让字母组成了“LOL”的字样,谁获胜。
\3. 如果所有格子都填满了,仍无法组成LOL,则平局。
小明试验了几次都输了,他很惭愧,希望你能用计算机帮他解开这个谜。输入
第一行,数字n(n<10),表示下面有n个初始局面。
接下来,n行,每行一个串(长度<20),表示开始的局面。
比如:“******”, 表示有6个空格。
“L”, 表示左边是一个字母L,它的右边是4个空格。输出
要求输出n个数字,表示对每个局面,如果小明先填,当K大师总是用最强着法的时候,小明的最好结果。
1 表示能赢,-1 表示必输,0 表示可以逼平。样例输入
4 *** L**L L**L***L L*****L
样例输出
0 -1 1 1
-
解题思路
一道非常好玩的博弈问题。由于局面够小,所以我们可以直接模拟,那么模拟过程中我们就要清楚一点,每个人都是采取最优策略,即赢 > > >平局 > > >输,如果能赢绝不平局,如果不能赢就平局,不到万不得已不能输。 按照这个思想,仔细分析局面即可。代码中已贴详细注释。其中比较重要的一点就是要记忆化局面,即存储好已经计算过的局面,这样才不会超时。
-
代码
/**
*@filename:填字母游戏
*@author: pursuit
*@CSDNBlog:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-04-02 15:57
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
string ss;
map<string,int> p;//记录计算过的确定局,避免重复计算。
char op[]={'L','O'};//定义我们的选择项。
int play(){
//我们的策略就是赢>平局>输。
//模拟游戏。
if(p.find(ss)!=p.end()){
//如果这个已经出现过,那么我们直接返回就可以。
return p[ss];
}
bool flag=false;//判断是否可能出现平局。
if(ss.size()<3){
//说明长度为3,说明不能组成LOL,双方只能平局。
p[ss]=0;
return 0;
}
//如果出现必胜局。
if(ss.find("LO*")!=ss.npos||ss.find("L*L")!=ss.npos||ss.find("*OL")!=ss.npos){
p[ss]=1;
return 1;
}
//如果空格已经填完了且还没出现LOL。
if(ss.find("*")==ss.npos&&ss.find("LOL")==ss.npos){
//说明这种局势为平局。
p[ss]=0;
return 0;
}
//特判完之后我们就需要模拟操作了。
for(int i=0;i<ss.size();i++){
if(ss[i]=='*'){
//我们则有两种选择。
for(int j=0;j<2;j++){
ss[i]=op[j];
//递归去判断。
if(ss.find("LO*")!=ss.npos||ss.find("L*L")!=ss.npos||ss.find("*OL")!=ss.npos){
//说明必输,我们直接恢复局面。
ss[i]='*';
continue;
}
int result=play();
//判断是否可行。
ss[i]='*';//还原状态。
if(result==-1){
//这个结果说明对方先手必输,我们必赢。
p[ss]=1;
return 1;
}
else if(result==0){
//说明平局,我们标记出现了平局,但目前我们还不能选择这个决策。
flag=true;
continue;
}
else{
//说明对面赢了,那么就更不行了。
continue;
}
}
}
}
//如果还不能赢,我们判断是否出现过平局输出即可。
if(flag){
p[ss]=0;
return 0;
}
else{
p[ss]=-1;
return -1;
}
}
int main(){
int n;
while(cin>>n){
for(int i=0;i<n;i++){
cin>>ss;
cout<<play()<<endl;
}
}
return 0;
}
第五题 区间移位
-
问题重现
数轴上有n个闭区间:D1,…,Dn。其中区间Di用一对整数[ai, bi]来描述,满足ai< bi。
已知这些区间的长度之和至少有10000。
所以,通过适当的移动这些区间,你总可以使得他们的“并”覆盖[0, 10000]
也就是说[0, 10000]这个区间内的每一个点都落于至少一个区间内。
你希望找一个移动方法,使得位移差最大的那个区间的位移量最小。
具体来说,假设你将Di移动到[ai+ci, bi+ci]这个位置。你希望使得maxi{|ci|} 最小。输入
输入的第一行包含一个整数n,表示区间的数量。
接下来有n行,每行2个整数ai, bi,以一个空格分开,表示区间[ai, bi]。
保证区间的长度之和至少是10000。
1 < = n < = 10000 , 0 < = a i < b i < = 10000 1 <= n <= 10000,0 <= a_i< b_i<= 10000 1<=n<=10000,0<=ai<bi<=10000输出
输出一个数字,表示答案。如果答案是整数,只输出整数部分。
如果答案不是整数,输出时四舍五入保留一位小数。【样例输入】 2 10 5010 4980 9980 【样例输出】 20 【样例说明】 第一个区间往左移动10;第二个区间往右移动20。 【样例输入】 4 0 4000 3000 5000 5001 8000 7000 10000 【样例输出】 0.5 【样例说明】 第2个区间往右移0.5;第3个区间往左移0.5即可。
-
解题思路
对于这种答案处于我们已知的范围类的题,我们是可以利用二分法来枚举这个答案的,我想对于二分法处理起来很简单,我们需要先将区间$\times 2 , 这 这 样 能 保 证 我 们 枚 举 的 时 候 不 会 精 度 丢 失 。 关 键 就 是 判 断 我 们 枚 举 的 答 案 是 否 可 行 。 这 是 最 难 也 是 最 重 要 的 一 点 。 = = 首 先 , 我 们 需 要 对 区 间 进 行 处 理 , 即 对 它 们 进 行 排 序 , 为 了 处 理 方 便 , 我 们 统 一 以 右 区 间 端 点 作 为 主 衡 量 标 准 , 左 区 间 端 点 副 衡 量 标 准 , 这 样 是 因 为 我 们 处 理 是 从 左 到 右 的 。 = = 我 们 确 定 一 个 变 量 2,这这样能保证我们枚举的时候不会精度丢失。关键就是判断我们枚举的答案是否可行。这是最难也是最重要的一点。==首先,我们需要对区间进行处理,即对它们进行排序,为了处理方便,我们统一以右区间端点作为主衡量标准,左区间端点副衡量标准,这样是因为我们处理是从左到右的。== 我们确定一个变量 2,这这样能保证我们枚举的时候不会精度丢失。关键就是判断我们枚举的答案是否可行。这是最难也是最重要的一点。==首先,我们需要对区间进行处理,即对它们进行排序,为了处理方便,我们统一以右区间端点作为主衡量标准,左区间端点副衡量标准,这样是因为我们处理是从左到右的。==我们确定一个变量last$来维护当前所填的区间的最右端点(初始为 0 0 0)。那么满足条件的标准就是其值大于 20000 20000 20000(因为区间长度被我们放大了)。然后在维护这个变量的时候就是不断往 l a s t last last处插入区间。已贴详细注释。
-
代码
/**
*@filename:区间移位
*@author: pursuit
*@CSDNBlog:unique_pursuit
*@email: 2825841950@qq.com
*@created: 2021-04-02 18:53
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;
int n;//区间的数量。
struct Interval{
int l,r;//左区间端点和右区间端点。
bool operator<(const Interval A){
if(A.r==r)return l<A.l;
return r<A.r;
}
};
Interval intervals[maxn];
bool check(int len){
vector<Interval> temp(intervals,intervals+n);//利用临时数组来进行处理。
int last=0;//这个代表我们已经更新区间的最右边的点
while(true){
//我们枚举我们需要用的。
bool flag=false;
for(int i=0;i<temp.size();i++){
//判断该区间是否符合。
//判断是否可以移动,如果往左移都区间都在last右边,或者往右移区间都在last左边,这种情况必然不行。
if(temp[i].l-len<=last&&temp[i].r+len>=last){
flag=true;
if(temp[i].l+len>=last){
//说明可以往左或往右移动到last,这个时候就可以获取整个区间的长度
last=last+(temp[i].r-temp[i].l);//更新最右边的点。
}
else{
//如果移动距离不够,那么我们最大化移动,即往右移动len.
last+=(temp[i].r+len-last);
}
//移动成功,我们就删除这个区间。即使用了这个区间。下一次重新选择最优区间。避免选择了的区间干扰。
temp.erase(temp.begin()+i);
break;
}
}
//如果我们找不到合格的区间或者已经使用完区间了,那么我们直接退出。
if(temp.size()==0||flag==0){
break;
}
}
return last>=20000;//判断区间最右边的点是否满足条件。
}
void solve(){
//首先对这些进行排序。
sort(intervals,intervals+n);
int l=0,r=2e4,mid;//利用二分法枚举我们移动的区间位移量
while(l<r){
mid=(l+r)>>1;
if(check(mid)){
//可行就让这个变得更小。
r=mid;
}
else{
l=mid+1;
}
}
cout<<(float(l))/2.0<<endl;
}
int main(){
while(cin>>n){
for(int i=0;i<n;i++){
cin>>intervals[i].l>>intervals[i].r;//由于区间可能是小数,所以我们可以先扩大二倍,避免精度缺失。
intervals[i].l*=2,intervals[i].r*=2;
}
solve();
}
return 0;
}
第六题 数组操作
-
问题重现
给出一个长度为 n 的数组 {A},由 1 到 n 标号 , 你需要维护 m 个操作。
操作分为三种,输入格式为:
1 L R d,将数组中下标 L 到 R 的位置都加上 d,即对于 L<=i<=R,执行A[i]=A[i]+d。
2 L1 R1 L2 R2,将数组中下标为 L1 到 R1 的位置,赋值成 L2 到 R2 的值,保证 R1-L1=R2-L2。
换句话说先对 0<=i<=R2-L2 执行 B[i]=A[L2+i],再对 0<=i<=R1-L1 执行 A[L1+i]=B[i],其中 {B} 为一个临时数组。
3 L R,求数组中下标 L 到 R 的位置的和,即求出 ∑Ai(i=L到R)。输入
第一行一个整数 Case,表示测试点编号,其中 Case=0 表示该点为样例。
第二行包含两个整数 n,m。保证 1<=n,m<=10^5。
第三行包含 n 个整数 A_i,表示这个数组的初值。保证 0<=A_i<=10^5。
接下来 m 每行描述一个操作,格式如问题描述所示。
对于操作中提到每个数,满足 0<=d<=10^5,1<=L<=R<=n,1<=L1<=R1<=n,1<=L2<=R2<=n,R1-L1=R2-L2。输出
对于每次 3 操作输出一行一个数,表示求和的结果。
样例输入
0 5 6 1 2 3 4 5 2 1 3 3 5 3 3 5 1 2 4 2 3 3 5 2 1 3 3 5 3 1 5
样例输出
14 18 29
-
解题思路
这道题就是利用线段树来处理的,题解暂时搁置,待补。
d<=10^5,1<=L<=R<=n,1<=L1<=R1<=n,1<=L2<=R2<=n,R1-L1=R2-L2。
输出
对于每次 3 操作输出一行一个数,表示求和的结果。
样例输入
0 5 6 1 2 3 4 5 2 1 3 3 5 3 3 5 1 2 4 2 3 3 5 2 1 3 3 5 3 1 5
样例输出
14 18 29
-
解题思路
这道题就是利用线段树来处理的,题解暂时搁置,待补。
-
代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人