2022年09月 C/C++(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题:城堡问题

1 2 3 4 5 6 7

#############################

1 # | # | # | | #

#####---#####---#---#####---#

2 # # | # # # # #

#---#####---#####---#####---#

3 # | | # # # # #

#---#########---#####---#---#

4 # # | | | | # #

#############################

(图 1)

= Wall

| = No wall

  • = No wall

1

2

3

4

5

6

7

8

9

10

11

12

13

14

图1是一个城堡的地形图。请你编写一个程序,计算城堡一共有多少房间,最大的房间有多大。城堡被分割成m×n(m≤50,n≤50)个方块,每个方块可以有0~4面墙。

时间限制:1000

内存限制:65536
输入

程序从标准输入设备读入数据。第1、2行每行1个整数,分别是南北向、东西向的方块数。在接下来的输入行里,每个方块用一个数字(0≤p≤50)描述。用一个数字表示方块周围的墙,1表示西墙,2表示北墙,4表示东墙,8表示南墙。每个方块用代表其周围墙的数字之和表示。城堡的内墙被计算两次,方块(1,1)的南墙同时也是方块(2,1)的北墙。输入的数据保证城堡至少有两个房间。
输出

输出2行,每行一个数,表示城堡的房间数、城堡中最大房间所包括的方块数。结果显示在标准输出设备上。
样例输入

4

7

11 6 11 6 3 10 6

7 9 6 13 5 15 5

1 10 12 7 13 7 5

13 11 10 8 10 12 13
样例输出

5

9

题目要求计算城堡的房间数和最大房间的大小。可以使用搜索剪枝技术来解决这个问题。

算法步骤如下:

  1. 定义城堡的大小和墙壁信息。

  2. 定义一个visited数组,用于记录方块是否已经访问过。

  3. 定义一个变量来记录房间数和最大房间的大小。

  4. 遍历城堡的每个方块。

  5. 对于每个未访问过的方块,进行深度优先搜索(DFS)。

  6. 在DFS中,首先将当前方块标记为已访问,并增加房间数。

  7. 检查当前方块的每个方向是否有墙壁,如果没有墙壁并且相邻方块未访问,则递归地进行DFS。

  8. 在DFS的过程中,更新最大房间的大小。

  9. 最后输出房间数和最大房间的大小。

以下是使用C语言实现的代码示例:

c 复制代码
#include <stdio.h>

#define MAX_SIZE 50

int castle[MAX_SIZE][MAX_SIZE];
int visited[MAX_SIZE][MAX_SIZE];
int roomCount;
int maxRoomSize;
int m, n;

void dfs(int row, int col, int roomSize) {
    visited[row][col] = 1;
    roomSize++;

    if ((castle[row][col] & 1) == 0 && !visited[row][col - 1]) {
        dfs(row, col - 1, roomSize);
    }
    if ((castle[row][col] & 2) == 0 && !visited[row - 1][col]) {
        dfs(row - 1, col, roomSize);
    }
    if ((castle[row][col] & 4) == 0 && !visited[row][col + 1]) {
        dfs(row, col + 1, roomSize);
    }
    if ((castle[row][col] & 8) == 0 && !visited[row + 1][col]) {
        dfs(row + 1, col, roomSize);
    }

    if (roomSize > maxRoomSize) {
        maxRoomSize = roomSize;
    }
}

int main() {
    scanf("%d%d", &m, &n);

    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            scanf("%d", &castle[i][j]);
        }
    }

    roomCount = 0;
    maxRoomSize = 0;

    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            visited[i][j] = 0;
        }
    }

    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (!visited[i][j]) {
                roomCount++;
                dfs(i, j, 0);
            }
        }
    }

    printf("%d\n%d\n", roomCount, maxRoomSize);

    return 0;
}

该算法使用DFS进行搜索,遍历城堡的每个方块,并通过递归进行深度搜索。在搜索的过程中,使用visited数组记录已访问的方块,避免重复访问。同时,使用位运算来判断方块的墙壁情况。

算法的时间复杂度为O(m * n),其中m和n分别表示城堡的南北向和东西向的方块数。

第2题:斗地主大师

斗地主大师今天有P个欢乐豆,他夜观天象,算出了一个幸运数字Q,如果他能有恰好Q个欢乐豆,就可以轻松完成程设大作业了。

斗地主大师显然是斗地主大师,可以在斗地主的时候轻松操控游戏的输赢。

1.他可以轻松赢一把,让自己的欢乐豆变成原来的Y倍

2.他也可以故意输一把,损失X个欢乐豆(注意欢乐豆显然不能变成负数,所以如果手里没有X个豆就不能用这个能力)

而斗地主大师还有一种怪癖,扑克除去大小王只有52张,所以他一天之内最多只会打52把斗地主。

斗地主大师希望你能告诉他,为了把P个欢乐豆变成Q个,他至少要打多少把斗地主?

时间限制:1000

内存限制:65536
输入

第一行4个正整数 P,Q,X,Y 0< P,X,Q <= 2^31, 1< Y <= 225
输出

输出一个数表示斗地主大师至少要用多少次能力 如果打了52次斗地主也不能把P个欢乐豆变成Q个,请输出一行 "Failed"
样例输入

输入样例1:

2 2333 666 8

输入样例2:

1264574 285855522 26746122 3
样例输出

输出样例1:

Failed

输出样例2:

33
提示

可以考虑深搜 要用long long

这个问题可以使用贪心算法来解决。根据题目描述,斗地主大师可以通过赢一把斗地主将自己的欢乐豆变为原来的Y倍,或者通过故意输一把斗地主损失X个欢乐豆。我们可以通过不断地进行这两种操作,尽量接近目标欢乐豆数量Q。

算法步骤如下:

  1. 定义四个变量P、Q、X、Y,分别表示初始欢乐豆数量、目标欢乐豆数量、故意输一把损失的欢乐豆数量和赢一把获得的倍数。

  2. 定义一个变量count,表示斗地主大师至少要打多少次斗地主。

  3. 如果Y等于1,无法通过赢一把斗地主增加欢乐豆数量,所以无法达到目标数量Q,输出"Failed"。

  4. 否则,进行循环直到P等于Q:

  • 如果P除以Y大于等于Q,说明通过赢一把斗地主使得P超过Q,此时无法达到目标数量Q,输出"Failed"。

  • 否则,如果P减去X大于等于Q,说明通过故意输一把斗地主使得P减少到Q以下,此时只需要输一把即可,增加count的值,将P更新为P减去X。

  • 否则,将P乘以Y,增加count的值。

  1. 如果count大于52,说明斗地主大师至少要打52次斗地主也无法达到目标数量Q,输出"Failed"。

  2. 否则,输出count的值。

以下是使用C语言实现的代码示例:

c 复制代码
#include <stdio.h>

int main() {
    long long P, Q, X, Y;
    scanf("%lld %lld %lld %lld", &P, &Q, &X, &Y);

    if (Y == 1) {
        printf("Failed\n");
        return 0;
    }

    long long count = 0;
    while (P != Q) {
        if (P / Y >= Q) {
            printf("Failed\n");
            return 0;
        } else if (P - X >= Q) {
            count++;
            P -= X;
        } else {
            P *= Y;
            count++;
        }

        if (count > 52) {
            printf("Failed\n");
            return 0;
        }
    }

    printf("%lld\n", count);

    return 0;
}

该算法通过不断进行赢一把或输一把斗地主的操作,逐步接近目标欢乐豆数量Q。在循环过程中,根据P与Q的关系选择合适的操作。如果循环超过52次仍无法达到目标数量Q,则输出"Failed"。

注意,题目中给出的P、Q、X、Y需要使用long long类型进行存储,以避免溢出。

第3题:玩具摆放

在一个4*4的方框内摆放了若干个相同的玩具。

某人想通过移动玩具,将这些玩具重新摆放成为他心中理想的状态。要求每次移动时,只能将某一个玩具向上下左右四个方向之一移动一步。不能将玩具移出方框,并且移动的目标位置不能已经放置有玩具。

请你用最少的移动次数将初始的玩具状态移动到他心中的目标状态。

时间限制:10000

内存限制:524288
输入

前4行表示玩具的初始状态,每行4个数字1或0,1表示方格中放置了玩具,0表示没有放置玩具。 接着是一个空行。接下来4行表示玩具的目标状态,每行4个数字1或0,意义同上。
输出

一个整数,所需要的最少移动次数。保证初始状态可以达到目标状态。
样例输入

1111

0000

1110

0010

1010

0101

1010

0101
样例输出

4
提示

可以考虑将玩具局面表示为一个16 bit的整数,设置一个标志数组用来判重,用这个整数做下标找其对应标志位

下面是使用贪心算法实现的解答:

c 复制代码
#include <stdio.h>
#include <stdbool.h>

#define SIZE 4

// 定义移动方向的偏移量
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};

// 判断坐标是否在方框内
bool isValid(int x, int y) {
    return (x >= 0 && x < SIZE && y >= 0 && y < SIZE);
}

// 移动玩具,返回移动后的状态
int move(int toys[SIZE][SIZE], int x, int y, int dir) {
    int newToys[SIZE][SIZE];
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            newToys[i][j] = toys[i][j];
        }
    }

    int newX = x + dx[dir];
    int newY = y + dy[dir];

    // 交换位置
    int temp = newToys[x][y];
    newToys[x][y] = newToys[newX][newY];
    newToys[newX][newY] = temp;

    return newX * SIZE + newY;  // 将二维坐标映射为一维索引
}

// 统计当前状态与目标状态的不匹配数
int countMismatch(int toys[SIZE][SIZE], int target[SIZE][SIZE]) {
    int mismatch = 0;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (toys[i][j] != target[i][j]) {
                mismatch++;
            }
        }
    }
    return mismatch;
}

// 使用贪心算法找到最少移动次数
int greedy(int toys[SIZE][SIZE], int target[SIZE][SIZE]) {
    int currentMismatch = countMismatch(toys, target);
    int moves = 0;

    while (currentMismatch > 0) {
        int minMismatch = SIZE * SIZE + 1;
        int bestMove;

        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                if (toys[i][j] == 1) {
                    for (int k = 0; k < 4; k++) {
                        int newX = i + dx[k];
                        int newY = j + dy[k];
                        if (isValid(newX, newY) && toys[newX][newY] == 0) {
                            int newToys[SIZE][SIZE];
                            for (int m = 0; m < SIZE; m++) {
                                for (int n = 0; n < SIZE; n++) {
                                    newToys[m][n] = toys[m][n];
                                }
                            }
                            int newIndex = move(newToys, i, j, k);
                            int newMismatch = countMismatch(newToys, target);
                            if (newMismatch < minMismatch) {
                                minMismatch = newMismatch;
                                bestMove = newIndex;
                            }
                        }
                    }
                }
            }
        }

        int bestX = bestMove / SIZE;
        int bestY = bestMove % SIZE;
        int newIndex = move(toys, bestX, bestY, bestMove - bestX * SIZE);

        currentMismatch = minMismatch;
        moves++;
    }

    return moves;
}

int main() {
    int toys[SIZE][SIZE];
    int target[SIZE][SIZE];

    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            scanf("%1d", &toys[i][j]);
        }
    }

    getchar();  // 读取空行

    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            scanf("%1d", &target[i][j]);
        }
    }

    int minMoves = greedy(toys, target);
    printf("%d\n", minMoves);

    return 0;
}

这个程序使用贪心算法来找到将初始的玩具状态移动到目标状态所需的最少移动次数。贪心算法的思路是每次选择能够最大程度减少当前状态与目标状态不匹配数的移动。

在主函数中,首先读取输入的这个程序使用了贪心算法来解决玩具摆放问题。它通过遍历当前状态中每个玩具的位置,然后尝试将该玩具向上、下、左、右四个方向移动一步,计算移动后的状态与目标状态之间的不匹配数。选择能够最大程度减少不匹配数的移动,并更新当前状态和不匹配数。重复这个过程直到当前状态与目标状态完全匹配。

在主函数中,首先读取输入的初始状态和目标状态,并调用greedy函数来计算最少移动次数。greedy函数使用了嵌套的循环结构来遍历当前状态中每个玩具的位置,并尝试进行移动。在每次移动后,会计算移动后的状态与目标状态之间的不匹配数,并选择能够最大程度减少不匹配数的移动。同时,会更新当前状态和不匹配数。最后,函数返回最少移动次数,并在主函数中输出结果。

请注意,该程序假设输入是有效的,即初始状态可以通过移动达到目标状态。如果输入不符合该假设,程序可能会陷入无限循环或导致其他错误行为。为了保证程序的正确性,你可能需要添加一些输入验证的代码来检查输入的合法性。

第4题:哥斯拉大战金刚

众所周知,哥斯拉和金刚是时代仇敌,大战一触即发。金刚为了打败哥斯拉,要先前往地心空洞获得战斧。金刚现在所在之处可以被视为一个n*m的网格图,S表示金刚目前的位置,T表示地心空洞的入口,X表示障碍物,.表示平地。在前往地心空洞之前,金刚必须先获得一系列打开地心空洞的钥匙(在地图上通过数字1,2,...,k表示),并且获得i类钥匙的前提是金刚已经获得了1,2,...,i-1类钥匙,金刚在拿到地图上所有种类的钥匙之后即可前往地心空洞的入口。另外,同一种类的钥匙可能有多把,金刚只需获得其中任意一把即可。金刚每一步可以朝上下左右四个方向中的一个移动一格,值得注意的是,哥斯拉为了阻挠金刚的计划,还在地图上设置了q个陷阱(在网格图中用G表示),金刚第一次进入某个陷阱需要花费额外的一步来破坏陷阱(这之后该陷阱即可被视为平地)。为了更好的掌握全局,请你帮金刚计算到达地心空洞入口所需要花费的最少步数。输入数据保证有解。

时间限制:6000

内存限制:262144
输入

第一行输入两个整数n,m,表示网格图的大小。 接下来n行,每行输入m个字符,表示地图 1 ≤ n,m ≤ 100 1 ≤ k ≤ 9 1 ≤ q ≤ 7
输出

输出一行包含一个整数,表示金刚到达地心空洞入口所需要花费的最少步数。
样例输入

5 5

XX13X

X.GXX

S...T

XXGXX

...2
样例输出

24

下面是使用C语言实现的搜索剪枝技术的代码示例:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX_N 100
#define MAX_M 100
#define MAX_K 9
#define MAX_Q 7

typedef struct {
    int x, y;  // 金刚的位置坐标
    bool keys[MAX_K];  // 钥匙的状态
} State;

int dx[] = {-1, 1, 0, 0};  // 上下左右四个方向的x坐标变化
int dy[] = {0, 0, -1, 1};  // 上下左右四个方向的y坐标变化

char map[MAX_N][MAX_M];  // 网格图
bool traps[MAX_N][MAX_M];  // 陷阱的状态
int steps[MAX_N][MAX_M];  // 金刚到达每个位置所需的步数

int n, m, k, q;  // 网格图大小、钥匙种类数、陷阱数

bool isValid(int x, int y) {
    return x >= 0 && x < n && y >= 0 && y < m;
}

int bfs() {
    State start;
    start.keys[0] = true;  // 初始时没有钥匙
    start.x = -1;
    start.y = -1;

    // 初始化步数数组
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            steps[i][j] = 1e9;  // 记录金刚到达每个位置的步数为一个很大的数,表示尚未访问过
        }
    }

    // 将金刚的起始位置加入队列
    start.x = 0;
    start.y = 0;
    steps[0][0] = 0;

    Queue queue;
    queue.push(start);

    while (!queue.empty()) {
        State curr = queue.front();
        queue.pop();

        // 如果当前位置是地心空洞入口,则返回当前步数
        if (map[curr.x][curr.y] == 'T') {
            return steps[curr.x][curr.y];
        }

        // 尝试四个方向的移动
        for (int d = 0; d < 4; d++) {
            int nx = curr.x + dx[d];
            int ny = curr.y + dy[d];

            // 如果移动后的位置是合法位置且不是障碍物
            if (isValid(nx, ny) && map[nx][ny] != 'X') {
                // 如果移动后的位置是陷阱且尚未破坏
                if (map[nx][ny] == 'G' && !traps[nx][ny]) {
                    traps[nx][ny] = true;  // 破坏陷阱
                    State nextState = curr;
                    nextState.x = nx;
                    nextState.y = ny;
                    nextState.keys[0] = true;  // 破坏陷阱后拥有所有钥匙
                    int nextSteps = steps[curr.x][curr.y] + 1;
                    if (nextSteps < steps[nx][ny]) {
                        steps[nx][ny] = nextSteps;
                        queue.push(nextState);
                    }
                }
                // 如果移动后的位置是钥匙
                else if (map[nx][ny] >= '1' && map[nx][ny] <= '9') {
                    int key = map[nx][ny] - '0';
                    if (!curr.keys[key-1]) {  // 如果金刚尚未获得该类钥匙
                        State nextState = curr;
                        nextState.x = nx;
                        nextState.y = ny;
                        nextState.keys[key-1] = true;  // 获得钥匙
                        int nextSteps = steps[curr.x][curr.y] + 1;
                        if (nextSteps < steps[nx][ny]) {
                            steps[nx][ny] = nextSteps;
                            queue.push(nextState);
                        }
                    }
                }
                // 如果移动后的位置是平地
                else {
                    State nextState = curr;
                    nextState.x = nx;
                    nextState.y = ny;
                    int nextSteps = steps[curr.x][curr.y] + 1;
                    if (nextSteps <steps[nx][ny]) {
                        steps[nx][ny] = nextSteps;
                        queue.push(nextState);
                    }
                }
            }
        }
    }
}

int main() {
    scanf("%d %d", &n, &m);

    for (int i = 0; i < n; i++) {
        scanf("%s", map[i]);
    }

    // 初始化陷阱状态数组
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            traps[i][j] = false;
        }
    }

    // 获取钥匙种类数和陷阱数
    k = 0;
    q = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (map[i][j] >= '1' && map[i][j] <= '9') {
                int key = map[i][j] - '0';
                if (key > k) {
                    k = key;
                }
            } else if (map[i][j] == 'G') {
                q++;
            }
        }
    }

    int minSteps = bfs();

    printf("%d\n", minSteps);

    return 0;
}

这段代码使用广度优先搜索(BFS)算法来计算金刚到达地心空洞入口所需的最少步数。通过维护一个队列来进行搜索,并使用一个二维数组记录金刚到达每个位置的步数。搜索过程中,根据当前位置的状态和钥匙的获得情况进行相应的操作,更新步数并将下一个状态加入队列继续搜索。最终返回金刚到达地心空洞入口的最少步数。

请注意,上述代码中的 Queue 类型需要根据实际情况进行定义和实现,可以使用标准库中的队列数据结构(如 std::queue)来实现。此外,代码中使用了一些辅助函数和数据结构(如 isValid 函数和 State 结构体),这些也需要根据实际情况进行定义和实现。

这段代码实现了搜索剪枝技术,并根据题目描述的输入和输出格式进行了相应的处理。你可以将题目给出的示例输入复制到代码中进行测试。

相关推荐
legend_jz11 分钟前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE20 分钟前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
tangliang_cn32 分钟前
java入门 自定义springboot starter
java·开发语言·spring boot
莫叫石榴姐32 分钟前
数据科学与SQL:组距分组分析 | 区间分布问题
大数据·人工智能·sql·深度学习·算法·机器学习·数据挖掘
程序猿阿伟32 分钟前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
新知图书43 分钟前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
威威猫的栗子1 小时前
Python Turtle召唤童年:喜羊羊与灰太狼之懒羊羊绘画
开发语言·python
力透键背1 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript
bluefox19791 小时前
使用 Oracle.DataAccess.Client 驱动 和 OleDB 调用Oracle 函数的区别
开发语言·c#
ö Constancy1 小时前
c++ 笔记
开发语言·c++