第八届蓝桥杯(软件类)决赛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<=100000<=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
  • 解题思路

    这道题就是利用线段树来处理的,题解暂时搁置,待补。

  • 代码

posted @   unique_pursuit  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示