第六届蓝桥杯大赛个人赛决赛(软件类) C++A组真题题解

题目链接

A组真题

题目结构

题目类型分值
第一题结果填空19分
第二题结果填空25分
第三题代码填空31分
第四题程序设计41分
第五题程序设计75分
第六题程序设计99分

第一题 方格填数

  • 问题重现

    在2行5列的格子中填入1到10的数字。
    要求:
    相邻的格子中的数,右边的大于左边的,下边的大于上边的。
    如下图所示的2种,就是合格的填法。
    在这里插入图片描述
    请你计算一共有多少种可能的方案。
    请提交该整数,不要填写任何多余的内容(例如:说明性文字)。

  • 解题思路
    对于这种类型的题,我们有两种方法可以解决。一种是暴力全排列,一种是dfs搜索。对于暴力全排列枚举所有情况,这里显然是可以的,因为只有 10 10 10个空,也就是 A 10 10 A_{10}^{10} A1010,肯定能跑出来。而 d f s dfs dfs显然更快,因为我们在搜索过程中可以即是排除不可能的路径,即剪枝。这里给出 d f s dfs dfs搜索的一般思路:

标记数组
放数的数组

int check(参数)
{

    if(满足条件)
        return 1;
    else
        return 0;
}//这个dfs合不合格,能不能继续下一个

去做每一步
int dfs(int step)
{
    判断边界;
    {
        相应操作;
    }
    尝试每一种可能
    {
        满足check条件
        {
            标记;
            进行下一步递归;
            恢复初始状态(取消标记),方便回溯
        }
    }
}

如果能够理解这个,那么就跟做模板题一样。该题的主要细节就是判断条件,这跟你的元素放置位置和搜索顺序有关。具体看代码。

  • 暴力全排列代码
/**
* @filename:方格填数.cbp
* @Author : pursuit
* @Blog:unique_pursuit
* @email: 2825841950@qq.com
* @Date : 2021-03-14-20.36.56
*/
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn=1e5+5;
const int mod=1e9+7;

int a[]={1,2,3,4,5,6,7,8,9,10};//按字典升序。方便全排列不遗漏
//全排列所有结果,判断合格方格。答案42.
void solve(){
    int result=0;
    do{
        bool flag=true;
        for(int i=0;i<4;i++){
            if(a[i]>a[i+1]){
                flag=false;
                break;
            }
        }
        if(!flag)continue;
        for(int i=5;i<9;i++){
            if(a[i]>a[i+1]){
                flag=false;
                break;
            }
        }
        if(!flag)continue;
        for(int i=0;i<=4;i++){
            if(a[i]>a[i+5]){
                flag=false;
                break;
            }
        }
        if(!flag)continue;
        result++;
    }while(next_permutation(a,a+10));
    cout<<result<<endl;
}
int main(){
    solve();
    return 0;
}

  • dfs搜索代码
/**
* @filename:方格填数.cbp
* @Author : pursuit
* @Blog:unique_pursuit
* @email: 2825841950@qq.com
* @Date : 2021-03-14-20.47.27
*/
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn=1e5+5;
const int mod=1e9+7;

//dfs,这里有个细节就是我们怎么填这个方格,为了让相邻数与其编号之间有联系,我们从上往下开始填。若为奇数编号,说明在上边。
bool vis[11];//vis[i]表示数字i是否被填充过。
int num[11];//代表我们的方格。
int result=0;//统计方案数。
bool check(int step,int x){
    if(step%2){
        if(x>num[step-2]){
            return true;
        }
    }
    else{
        if(x>num[step-1]&&x>num[step-2]){
            return true;
        }
    }
    return false;
}
void dfs(int step){
    //step代表我们此时填的第step的元素。
    if(step>10){
        //说明10个数已经填完了。
        result++;
        return;
    }
    for(int i=1;i<=10;i++){
        //判断是否符合条件。
        if(!vis[i]&&check(step,i)){
            vis[i]=true;
            num[step]=i;
            dfs(step+1);
            vis[i]=false;
        }
    }
}
void solve(){
    dfs(1);
    cout<<result<<endl;
}
int main(){
    solve();
    return 0;
}


  • 答案
    42 42 42

第二题 四阶幻方

  • 问题重现

    把1~16的数字填入4x4的方格中,使得行、列以及两个对角线的和都相等,
    满足这样的特征时称为:四阶幻方。
    四阶幻方可能有很多方案。如果固定左上角为1,请计算一共有多少种方案。
    比如:
    1 2 15 16
    12 14 3 5
    13 7 10 4
    8 11 6 9
    以及:
    1 12 13 8
    2 14 7 11
    15 3 10 6
    16 5 4 9
    就可以算为两种不同的方案。
    请提交左上角固定为1时的所有方案数字,
    不要填写任何多余内容或说明文字。

  • 解题思路
    这道题和第一题一样,那么我们是不是也有两种方法来做这题呢?先看暴力全排列,我们算一下时间复杂度,高达 A 15 15 = 2004310016 A_{15}^{15}=2004310016 A1515=2004310016,我自己也亲测等了好久也跑不出来,因为全排列会枚举所有的情况,不会剪枝,在这种情况下是非常不友好的,所以我们只能考虑dfs搜索剪枝了。怎么搜索呢?我们从行开始搜索,当每一次行或列形成的时候我们就进行判断是否符合,(由于行列和要相等,说明其值为方格数总和的四分之一,即 34 34 34。)同时在 d f s dfs dfs中,一定要注意标记状态,同时也一定要注意还原状态,这样才能保证每一条路径都搜索过。清楚了这些,那处理好其余的细节这道题目就出来了。

  • 代码

/**
* @filename:四阶幻方.cbp
* @Author : pursuit
* @Blog:unique_pursuit
* @email: 2825841950@qq.com
* @Date : 2021-03-14-21.36.17
*/
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn=1e5+5;
const int mod=1e9+7;

//dfs
//易知,行,列以及对角线上的和为34,因为其为1到16的和除以4.
bool vis[17];//vis[i]表示数字i是否已经被填充。
int a[4][4];//方格。
int result=0;
bool check(int x,int y){
    if(x<3){
        //说明方格暂未成型,我们只能判断所在行是否成型。
        if(y<3)return true;
        if(a[x][0]+a[x][1]+a[x][2]+a[x][3]==34){
            return true;
        }
        return false;
    }
    else{
        //开始判断y。
        if(y==0){
            //说明第一列和对角线都已成型。
            if(a[0][0]+a[1][0]+a[2][0]+a[3][0]!=34||a[0][3]+a[1][2]+a[2][1]+a[3][0]!=34){
                return false;
            }
        }
        else if(y==1||y==2){
            //判断第二列和第三列的和是不是34.
            if(a[0][y]+a[1][y]+a[2][y]+a[3][y]!=34){
                return false;
            }
        }
        else{
            if(a[0][3]+a[1][3]+a[2][3]+a[3][3]!=34||a[0][0]+a[1][1]+a[2][2]+a[3][3]!=34){
                return false;
            }
        }
        return true;
    }
}
void dfs(int x,int y){
    //(x,y)表示的是我们当前正在填充的数坐标。
    if(x==4){
        //说明方格已经填完。
        result++;
        return;
    }
    for(int i=1;i<=16;i++){
        if(!vis[i]){
            vis[i]=true;
            a[x][y]=i;
            if(check(x,y)){
                if(y<3){
                    dfs(x,y+1);
                }
                else{
                    //说明需要换行。
                    dfs(x+1,0);
                }
            }
            //还原标记。
            vis[i]=false;
        }
    }
}
void solve(){
    //题目规定a[0][0]=1.
    vis[1]=true;
    a[0][0]=1;
    dfs(0,1);
    cout<<result<<endl;
}
int main(){
    solve();
    return 0;
}

  • 答案
    416 416 416

第三题 显示二叉树

  • 问题重现

    排序二叉树的特征是:
    某个节点的左子树的所有节点值都不大于本节点值。
    某个节点的右子树的所有节点值都不小于本节点值。
    为了能形象地观察二叉树的建立过程,小明写了一段程序来显示出二叉树的结构来。
    对于程序中的测试数据,应该显示出:
    在这里插入图片描述
    请分析程序逻辑,填写划线部分缺失的代码。
    注意,只填写缺少的部分,不要填写已有的代码或符号,也不要加任何说明文字。

#include <stdio.h>
#include <string.h>
#define N 1000
#define HEIGHT 100
#define WIDTH 1000

struct BiTree
{
    int v;
    struct BiTree* l;
    struct BiTree* r;
};

int max(int a, int b)
{
    return a>b? a : b;
}

struct BiTree* init(struct BiTree* p, int v)
{
    p->l = NULL;
    p->r = NULL;
    p->v = v;
    
    return p;
}

void add(struct BiTree* me, struct BiTree* the)
{
    if(the->v < me->v){
        if(me->l==NULL) me->l = the;
        else add(me->l, the);
    }
    else{
        if(me->r==NULL) me->r = the;
        else add(me->r, the);
    }
}

//获得子树的显示高度    
int getHeight(struct BiTree* me)
{
    int h = 2;
    int hl = me->l==NULL? 0 : getHeight(me->l);
    int hr = me->r==NULL? 0 : getHeight(me->r);
    
    return h + max(hl,hr);
}

//获得子树的显示宽度    
int getWidth(struct BiTree* me)
{
    char buf[100];
    sprintf(buf,"%d",me->v);
    int w = strlen(buf);
    if(me->l) w += getWidth(me->l);
    if(me->r) w += getWidth(me->r);
    return w;
}

int getRootPos(struct BiTree* me, int x){
    return me->l==NULL? x : x + getWidth(me->l);
}

//把缓冲区当二维画布用
void printInBuf(struct BiTree* me, char buf[][WIDTH], int x, int y)
{
    int p1,p2,p3,i;
    char sv[100];
    sprintf(sv, "%d", me->v);
    
    p1 = me->l==NULL? x : getRootPos(me->l, x);
    p2 = getRootPos(me, x);
    p3 = me->r==NULL? p2 : getRootPos(me->r, p2+strlen(sv));
    
    buf[y][p2] = '|';
    for(i=p1; i<=p3; i++) buf[y+1][i]='-';
    for(i=0; i<strlen(sv); i++) buf[y+1][p2+i]=sv[i];
    if(p1<p2) buf[y+1][p1] = '/';
    if(p3>p2) buf[y+1][p3] = '\\';
    
    if(me->l) printInBuf(me->l,buf,x,y+2);
    if(me->r) ____________________________________;  //填空位置
}

void showBuf(char x[][WIDTH])
{
    int i,j;
    for(i=0; i<HEIGHT; i++){
        for(j=WIDTH-1; j>=0; j--){
            if(x[i][j]==' ') x[i][j] = '\0';
            else break;
        }
        if(x[i][0])    printf("%s\n",x[i]);
        else break;
    }
}
    
void show(struct BiTree* me)
{
    char buf[HEIGHT][WIDTH];
    int i,j;
    for(i=0; i<HEIGHT; i++)
    for(j=0; j<WIDTH; j++) buf[i][j] = ' ';
    
    printInBuf(me, buf, 0, 0);
    showBuf(buf);
}

int main()
{    
    struct BiTree buf[N];    //存储节点数据
    int n = 0;              //节点个数
    init(&buf[0], 500); n++;  //初始化第一个节点

    add(&buf[0], init(&buf[n++],200));  //新的节点加入树中
    add(&buf[0], init(&buf[n++],509));
    add(&buf[0], init(&buf[n++],100));
    add(&buf[0], init(&buf[n++],250));
    add(&buf[0], init(&buf[n++],507));
    add(&buf[0], init(&buf[n++],600));
    add(&buf[0], init(&buf[n++],650));
    add(&buf[0], init(&buf[n++],450));
    add(&buf[0], init(&buf[n++],440));
    add(&buf[0], init(&buf[n++],220));
    
    show(&buf[0]);    
    return 0;    
}
  • 解题思路
    对于这种类型的题,尤其是这一道,虽然代码非常长,但我们发现其实对我们有用的就那几个打印的函数。在填空上一行代码,这是打印的左子树,我们发现,其中填充的参数为:子树结点,二维画布buf
    ,还有就是结点坐标了。我们要知道的是y代表的是层数,左右子树层数肯定是相同的,所以是x不一样,而p2是中间结点位置,sv为结点值,也就是结点长度,观察图中即可得,如果不填确定,我们可以打印看看是否与原图一致。

  • 答案

printInBuf(me->r,buf,p2+strlen(sv),y+2)

第四题 穿越雷区

  • 问题重现

    X星的坦克战车很奇怪,它必须交替地穿越正能量辐射区和负能量辐射区
    才能保持正常运转,否则将报废。
    某坦克需要从A区到B区去(A,B区本身是安全区,没有正能量或负能量
    特征),怎样走才能路径最短?
    已知的地图是一个方阵,上面用字母标出了A,B区,其它区都标了正号
    或负号分别表示正负能量辐射区。
    例如:
    A + - + -
    - + - - +
    - + + + -
    + - + - +
    B + - + -
    左右上
    坦克车只能水平或垂直方向上移动到相邻的区。
    数据格式要求:
    输入第一行是一个整数n,表示方阵的大小, 4<=n<100
    接下来是n行,每行有n个数据,可能是A,B,+,-中的某一个,
    中间用空格分开。
    A,B都只出现一次。
    要求输出一个整数,表示坦克从A区到B区的最少移动步数。
    如果没有方案,则输出-1
    例如:
    用户输入:
    5
    A + - + -
    - + - - +
    - + + + -
    + - + - +
    B + - + -
    则程序应该输出:
    10
    资源约定:
    峰值内存消耗 < 512M
    CPU消耗 < 1000ms

  • 解题思路
    标准的 b f s bfs bfs模板题,我们做这种题有步骤:
          1.将图用二维数组表示。
          2.将起点加入队列,并标记其入队状态。
          3.取队头节点,找到队头节点相邻的可访问的节点,加入队列(加入的顺序根据你查找的上下左右四个方向的顺序决定)。并标记结点入队状态。
          4.将队头元素出队。
          5.重复3,4步骤,直到找到终点。

    那么这道题我们也同样按这样的步骤来写。要注意的点就是,在输入的时候应该要以字符型输入,而不是字符串,因为题目中是以空格分割的。

  • 代码

/**
* @filename:穿越雷区.cbp
* @Author : pursuit
* @Blog:unique_pursuit
* @email: 2825841950@qq.com
* @Date : 2021-03-15-18.55.57
*/
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn=1e5+5;
const int mod=1e9+7;

int n;
char graph[105][105];
bool vis[105][105];//判断此点是否又被走过。
int go[4][2]={{0,1},{0,-1},{1,0},{-1,0}};//模拟行走方向。
int ax,ay;//存储A的位置。
typedef struct Node{
    int x,y,step;//step表示已经移动的次数。
    Node & operator=(const Node&a){
        x=a.x,y=a.y,step=a.step;
    }
}node;
void bfs(){
    queue<node> q;
    memset(vis,false,sizeof(vis));
    node head,temp;
    head.x=ax,head.y=ay,head.step=0;
    q.push(head);
    vis[ax][ay]=true;
//    for(int i=1;i<=n;i++){
//        for(int j=1;j<=n;j++){
//            cout<<graph[i][j];
//            j==n?cout<<endl:cout<<" ";
//        }
//    }
    while(!q.empty()){
        head=q.front();
//        cout<<"x:"<<head.x<<" y:"<<head.y<<" step:"<<head.step<<endl;
        q.pop();
        for(int i=0;i<4;i++){
            temp.x=head.x+go[i][0],temp.y=head.y+go[i][1],temp.step=head.step+1;
            if(temp.x<1||temp.x>n||temp.y<1||temp.y>n||vis[temp.x][temp.y]||graph[temp.x][temp.y]==graph[head.x][head.y])continue;
            if(graph[temp.x][temp.y]=='B'){
                cout<<temp.step<<endl;
                return;
            }
            vis[temp.x][temp.y]=true;
            q.push(temp);
        }
    }
    cout<<-1<<endl;
    return;
}
void solve(){
    bfs();
}
int main(){
    while(cin>>n){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                cin>>graph[i][j];
                //记录起点坐标。
                if(graph[i][j]=='A'){
                    ax=i,ay=j;
                }
            }
        }
        solve();
    }
    return 0;
}

第五题 切开字符串

  • 问题重现

    Pear有一个字符串,不过他希望把它切成两段。
    这是一个长度为N(<=10^5)的字符串。
    Pear希望选择一个位置,把字符串不重复不遗漏地切成两段,
    长度分别是t和N-t(这两段都必须非空)。
    Pear用如下方式评估切割的方案:
    定义“正回文子串”为:长度为奇数的回文子串。
    设切成的两段字符串中,前一段中有A个不相同的正回文子串,
    后一段中有B个不相同的非正回文子串,则该方案的得分为 A × B A\times B A×B
    注意,后一段中的B表示的是:“…非正回文…”,
    而不是: “…正回文…”。
    那么所有的切割方案中, A × B A\times B A×B的最大值是多少呢?
    【输入数据】
    输入第一行一个正整数N(<=10^5)
    接下来一行一个字符串,长度为N。该字符串仅包含小写英文字母。
    【输出数据】
    一行一个正整数,表示所求的A*B的最大值。
    【样例输入】
    10
    bbaaabcaba
    【样例输出】
    38
    【数据范围】
    对于20%的数据,N<=100
    对于40%的数据,N<=1000
    对于100%的数据,N<=10^5
    资源约定:
    峰值内存消耗 < 512M
    CPU消耗 < 1000ms
    请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
    所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
    注意: main函数需要返回0
    注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
    注意: 所有依赖的函数必须明确地在源文件中 #include , 不能通过工程设置而省略常用头文件。
    提交时,注意选择所期望的编译器类型。

  • 解题思路
    对于这道题,我们可以枚举其分割点位置,也就是前串的长度,那么后串的长度也自然得知。我们同样需要两个函数一个计算正回文子串的数量,一个是非正回文子串的数量。这里需要用到 m a p map map去重。对于字符串分割,我们可以使用string类内置的substr方法。具体看代码。

  • 代码

/**
* @filename:穿越雷区.cbp
* @Author : pursuit
* @Blog:unique_pursuit
* @email: 2825841950@qq.com
* @Date : 2021-03-15-18.55.57
*/
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn=1e5+5;
const int mod=1e9+7;

int n;
char graph[105][105];
bool vis[105][105];//判断此点是否又被走过。
int go[4][2]={{0,1},{0,-1},{1,0},{-1,0}};//模拟行走方向。
int ax,ay;//存储A的位置。
typedef struct Node{
    int x,y,step;//step表示已经移动的次数。
    Node & operator=(const Node&a){
        x=a.x,y=a.y,step=a.step;
    }
}node;
void bfs(){
    queue<node> q;
    memset(vis,false,sizeof(vis));
    node head,temp;
    head.x=ax,head.y=ay,head.step=0;
    q.push(head);
    vis[ax][ay]=true;
//    for(int i=1;i<=n;i++){
//        for(int j=1;j<=n;j++){
//            cout<<graph[i][j];
//            j==n?cout<<endl:cout<<" ";
//        }
//    }
    while(!q.empty()){
        head=q.front();
//        cout<<"x:"<<head.x<<" y:"<<head.y<<" step:"<<head.step<<endl;
        q.pop();
        for(int i=0;i<4;i++){
            temp.x=head.x+go[i][0],temp.y=head.y+go[i][1],temp.step=head.step+1;
            if(temp.x<1||temp.x>n||temp.y<1||temp.y>n||vis[temp.x][temp.y]||graph[temp.x][temp.y]==graph[head.x][head.y])continue;
            if(graph[temp.x][temp.y]=='B'){
                cout<<temp.step<<endl;
                return;
            }
            vis[temp.x][temp.y]=true;
            q.push(temp);
        }
    }
    cout<<-1<<endl;
    return;
}
void solve(){
    bfs();
}
int main(){
    while(cin>>n){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                cin>>graph[i][j];
                //记录起点坐标。
                if(graph[i][j]=='A'){
                    ax=i,ay=j;
                }
            }
        }
        solve();
    }
    return 0;
}

第六题 铺瓷砖(待补)

  • 问题重现

    为了让蓝桥杯竞赛更顺利的进行,主办方决定给竞赛的机房重新铺放瓷砖。机房可以看成一个n*m的矩形,而这次使用的瓷砖比较特别,有两种形状,如下图所示。在铺放瓷砖时,可以旋转。
    在这里插入图片描述
    主办方想知道,如果使用这两种瓷砖把机房铺满,有多少种方案。
    【输入格式】
    输入的第一行包含两个整数,分别表示机房两个方向的长度。
    【输出格式】
    输出一个整数,表示可行的方案数。这个数可能很大,请输出这个数除以65521的余数。
    【样例输入1】
    4 4
    【样例输出1】
    2
    【样例说明1】
    这两种方案如下下图所示:
    在这里插入图片描述
    【样例输入2】
    2 6
    【样例输出2】
    4
    【数据规模与约定】
    对于20%的数据,1<=n, m<=5。
    对于50%的数据,1<=n<=100,1<=m<=5。
    对于100%的数据,1<=n<=10^15,1<=m<=6。
    资源约定:
    峰值内存消耗 < 512M
    CPU消耗 < 5000ms

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