第六届蓝桥杯大赛个人赛决赛(软件类) 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人