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

第1题:书架

John最近买了一个书架用来存放奶牛养殖书籍,但书架很快被存满了,只剩最顶层有空余。

John共有N头奶牛(1 ≤ N ≤ 20,000),每头奶牛有自己的高度Hi(1 ≤ Hi ≤ 10,000),N头奶牛的总高度为S。书架高度为B(1 ≤ B ≤ S < 2,000,000,007).

为了到达书架顶层,奶牛可以踩着其他奶牛的背,像叠罗汉一样,直到他们的总高度不低于书架高度。当然若奶牛越多则危险性越大。为了帮助John到达书架顶层,找出使用奶牛数目最少的解决方案吧。

时间限制:10000

内存限制:65536
输入

第1行:空格隔开的整数N和B 第2~N+1行:第i+1行为整数Hi
输出

能达到书架高度所使用奶牛的最少数目
样例输入

6 40

6

18

11

13

19

11
样例输出

3

以下是使用贪心算法来解决书架问题的C语言代码:

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

#define MAX_N 20000

int compare(const void* a, const void* b) {
    return *(int*)a - *(int*)b;
}

int minCows(int heights[MAX_N], int n, int b) {
    qsort(heights, n, sizeof(int), compare);  // 将奶牛的高度排序
    
    int sum = 0;
    int count = 0;
    
    for (int i = n - 1; i >= 0; i--) {
        sum += heights[i];
        count++;
        
        if (sum >= b) {
            break;
        }
    }
    
    return count;
}

int main() {
    int n, b;
    int heights[MAX_N];
    
    // 读取输入
    scanf("%d %d", &n, &b);
    
    for (int i = 0; i < n; i++) {
        scanf("%d", &heights[i]);
    }
    
    // 使用贪心算法求解最少使用的奶牛数目
    int minCowsCount = minCows(heights, n, b);
    
    // 输出结果
    printf("%d\n", minCowsCount);
    
    return 0;
}

该算法使用贪心算法的思想,通过将奶牛的高度进行排序,并从最高的奶牛开始,逐个累加奶牛的高度,直到总高度不低于书架高度B为止。首先,使用快速排序算法对奶牛的高度进行排序。然后,从最高的奶牛开始累加其高度,并记录使用的奶牛数目。如果累加的总高度不低于书架高度B,则停止累加。最后,返回使用的奶牛数目作为答案。

第2题:棋盘问题

在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。

时间限制:1000

内存限制:65536
输入

输入含有多组测试数据。 每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n 当为-1 -1时表示输入结束。 随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
输出

对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
样例输入

2 1

#.

.#

4 4

...#

...#.

.#...

#...

-1 -1
样例输出

2

1

以下是使用回溯法来解决棋盘问题的C语言代码:

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

#define MAX_N 8

int count = 0;  // 记录可行的摆放方案数

bool isValid(char board[MAX_N][MAX_N], int row, int col, int n) {
    // 检查同一行是否已经有棋子
    for (int c = 0; c < col; c++) {
        if (board[row][c] == 'Q') {
            return false;
        }
    }
    
    // 检查左上方对角线是否已经有棋子
    for (int r = row, c = col; r >= 0 && c >= 0; r--, c--) {
        if (board[r][c] == 'Q') {
            return false;
        }
    }
    
    // 检查左下方对角线是否已经有棋子
    for (int r = row, c = col; r < n && c >= 0; r++, c--) {
        if (board[r][c] == 'Q') {
            return false;
        }
    }
    
    return true;
}

void backtrack(char board[MAX_N][MAX_N], int col, int n, int k) {
    if (col == n) {
        if (k == 0) {
            count++;
        }
        return;
    }
    
    for (int row = 0; row < n; row++) {
        if (board[row][col] == '#' && isValid(board, row, col, n)) {
            board[row][col] = 'Q';
            backtrack(board, col + 1, n, k - 1);
            board[row][col] = '#';
        }
    }
    
    backtrack(board, col + 1, n, k);
}

int chessboard(int n, int k, char board[MAX_N][MAX_N]) {
    count = 0;
    backtrack(board, 0, n, k);
    return count;
}

int main() {
    int n, k;
    char board[MAX_N][MAX_N];
    
    while (scanf("%d %d", &n, &k) == 2) {
        if (n == -1 && k == -1) {
            break;
        }
        
        for (int i = 0; i < n; i++) {
            scanf("%s", board[i]);
        }
        
        int result = chessboard(n, k, board);
        printf("%d\n", result);
    }
    
    return 0;
}

该算法使用回溯法的思想,通过递归地尝试在每个位置放置棋子,同时检查放置的位置是否满足要求。首先,定义isValid函数用于检查某个位置是否合法,即同一行、同一列以及对角线上没有其他棋子。然后,使用backtrack函数进行回溯搜索,从左到右、从上到下依次尝试在每个位置放置棋子。如果当前位置合法,就将棋子放置在该位置,并继续在下一列进行回溯搜索。如果当前位置不合法,则继续在下一行尝试。当回溯到最后一列时,如果已经放置了k个棋子,则找到了一个可行的摆放方案,将计数器加1。最后,调用chessboard函数进行求解,并输出可行的摆放方案数目C。

第3题:课程表

现在你总共有n门课需要选,记为0到n-1。在选修某些课程之前需要一些先修课程。例如,想要学习课程0,你需要先完成课程1,我们用一个匹配来表示他们:[0, 1]。给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

时间限制:1000

内存限制:65536
输入

多组数据。每组数据第一行是n和m,n表示有n门课程,m表示有m组依赖关系,接下来的m行是依赖关系的具体信息a b,表示第a门课程依赖第b门课程。 0<=n <=1000,0 <= m <= 4000 两组数据之间可能有空行
输出

对每组数据,能完成输出 True,不能完成输出 False
样例输入

2 1

1 0

2 2

1 0

0 1
样例输出

True

False
提示

示例2解释: 总共有2门课程。学习课程1之前,你需要先完成课程0;并且学习课程0之前,你还应先完成课程1。这是不可能的。

对于课程表问题,使用搜索剪枝技术是一个有效的方法。我们可以使用深度优先搜索(DFS)来遍历课程的依赖关系,并在搜索过程中进行剪枝以提高效率。

以下是使用搜索剪枝技术来解决课程表问题的C语言代码:

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

#define MAX_N 1000
#define MAX_M 4000

bool dfs(int course, int numCourses, int prerequisites[MAX_M][2], int numPrerequisites, bool visited[MAX_N], bool path[MAX_N]) {
    visited[course] = true;  // 标记当前课程为已访问
    path[course] = true;  // 将当前课程加入遍历路径中
    
    // 遍历当前课程的后续课程
    for (int i = 0; i < numPrerequisites; i++) {
        if (prerequisites[i][1] == course) {
            int nextCourse = prerequisites[i][0];
            
            // 判断是否存在环路,即当前课程在当前的遍历路径中
            if (path[nextCourse]) {
                return false;
            }
            
            // 如果后续课程未访问,则继续进行深度优先搜索
            if (!visited[nextCourse]) {
                if (!dfs(nextCourse, numCourses, prerequisites, numPrerequisites, visited, path)) {
                    return false;
                }
            }
        }
    }
    
    path[course] = false;  // 将当前课程从遍历路径中移除
    
    return true;
}

bool canFinish(int numCourses, int prerequisites[MAX_M][2], int numPrerequisites) {
    bool visited[MAX_N] = {false};  // 记录课程是否已访问
    bool path[MAX_N] = {false};  // 记录当前的遍历路径
    
    // 对每门课程进行深度优先搜索
    for (int i = 0; i < numCourses; i++) {
        if (!visited[i]) {
            if (!dfs(i, numCourses, prerequisites, numPrerequisites, visited, path)) {
                return false;
            }
        }
    }
    
    return true;
}

int main() {
    int numCourses, numPrerequisites;
    int prerequisites[MAX_M][2];
    
    while (scanf("%d %d", &numCourses, &numPrerequisites) == 2) {
        if (numCourses == 0 && numPrerequisites == 0) {
            break;
        }
        
        for (int i = 0; i < numPrerequisites; i++) {
            scanf("%d %d", &prerequisites[i][0], &prerequisites[i][1]);
        }
        
        bool result = canFinish(numCourses, prerequisites, numPrerequisites);
        printf("%s\n", result ? "True" : "False");
    }
    
    return 0;
}

该算法使用深度优先搜索(DFS)进行遍历,通过递归地搜索课程的依赖关系。首先,定义dfs函数进行深度优先搜索,其中course表示当前的课程,visited用于记录课程是否已访问,path用于记录当前的遍历路径。在搜索过程中,首先将当前课程标记为已访问,并将其加入遍历路径中。然后,遍历当前课程的后续课程,如果发现后续课程已经在当前的遍历路径中,则表示存在环路,返回false。如果后续课程未访问,则继续进行深度优先搜索。最后,将当前课程从遍历路径中移除,并返回true表示搜索完成。在canFinish函数中,对每门课程进行深度优先搜索,如果存在环路,则返回false;如果所有课程都能够完成搜索,返回true。最后,调用canFinish函数进行求解,并输出结果。

第4题:拯救公主

多灾多难的公主又被大魔王抓走啦!国王派遣了第一勇士阿福去拯救她。

身为超级厉害的术士,同时也是阿福的好伙伴,你决定祝他一臂之力。你为阿福提供了一张大魔王根据地的地图,上面标记了阿福和公主所在的位置,以及一些不能够踏入的禁区。你还贴心地为阿福制造了一些传送门,通过一个传送门可以瞬间转移到任意一个传送门,当然阿福也可以选择不通过传送门瞬移。传送门的位置也被标记在了地图上。此外,你还查探到公主所在的地方被设下了结界,需要集齐K种宝石才能打开。当然,你在地图上也标记出了不同宝石所在的位置。

你希望阿福能够带着公主早日凯旋。于是在阿福出发之前,你还需要为阿福计算出他最快救出公主的时间。

地图用一个R×C的字符矩阵来表示。字符S表示阿福所在的位置,字符E表示公主所在的位置,字符#表示不能踏入的禁区,字符$表示传送门,字符.表示该位置安全,数字字符0至4表示了宝石的类型。阿福每次可以从当前的位置走到他上下左右四个方向上的任意一个位置,但不能走出地图边界。阿福每走一步需要花费1个单位时间,从一个传送门到达另一个传送门不需要花费时间。当阿福走到宝石所在的位置时,就视为得到了该宝石,不需要花费额外时间。

时间限制:1000

内存限制:65536

输入

第一行是一个正整数T(1 <= T <= 10),表示一共有T组数据。 每一组数据的第一行包含了三个用空格分开的正整数R、C(2 <= R, C <= 200)和K,表示地图是一个R×C的矩阵,而阿福需要集齐K种宝石才能够打开拘禁公主的结界。 接下来的R行描述了地图的具体内容,每一行包含了C个字符。字符含义如题目描述中所述。保证有且仅有一个S和E。$的数量不超过10个。宝石的类型在数字0至4范围内,即不会超过5种宝石。

输出

对于每一组数据,输出阿福救出公主所花费的最少单位时间。若阿福无法救出公主,则输出"oop!"(只输出引号里面的内容,不输出引号)。每组数据的输出结果占一行。

样例输入

1

7 8 2

...

...S...#0.

.##...1...

.0#...

...1#...

...##E...

...1...

样例输出

11

根据题目要求,我们可以使用贪心算法来解决这个问题。贪心算法的思想是每一步都选择当前最优的解决方案,希望最终能够得到全局最优解。

下面是使用贪心算法解决该问题的C语言代码示例:

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

#define MAX_ROWS 200
#define MAX_COLS 200

int minTime; // 最少单位时间
int gemsCollected; // 已经收集到的宝石数量
char map[MAX_ROWS][MAX_COLS];
int visited[MAX_ROWS][MAX_COLS];
int rows, cols, requiredGems;
int startRow, startCol, endRow, endCol; // 起始位置和目标位置的行列坐标
int gems[MAX_ROWS][MAX_COLS]; // 记录宝石的类型

// 计算两点之间的曼哈顿距离
int manhattanDistance(int row1, int col1, int row2, int col2) {
    return abs(row1 - row2) + abs(col1 - col2);
}

// 贪心算法
void greedy(int row, int col, int time) {
    // 边界条件判断
    if (row < 0 || row >= rows || col < 0 || col >= cols || visited[row][col] || map[row][col] == '#')
        return;

    // 标记当前位置为已访问
    visited[row][col] = 1;

    // 到达目标位置,更新最少单位时间
    if (row == endRow && col == endCol) {
        if (gemsCollected == requiredGems) {
            if (time < minTime || minTime == -1)
                minTime = time;
        }
        visited[row][col] = 0; // 恢复当前位置为未访问状态
        return;
    }

    // 如果当前位置是宝石,则增加宝石数量
    if (gems[row][col] != -1)
        gemsCollected++;

    // 向四个方向进行搜索
    greedy(row - 1, col, time + 1); // 上
    greedy(row + 1, col, time + 1); // 下
    greedy(row, col - 1, time + 1); // 左
    greedy(row, col + 1, time + 1); // 右

    // 如果当前位置是宝石,则减少宝石数量
    if (gems[row][col] != -1)
        gemsCollected--;

    // 恢复当前位置为未访问状态
    visited[row][col] = 0;
}

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

    while (numCases--) {
        scanf("%d %d %d", &rows, &cols, &requiredGems);

        // 初始化
        minTime = -1;
        gemsCollected = 0;

        // 读取地图数据
        for (int i = 0; i < rows; i++) {
            scanf("%s", map[i]);
            for (int j = 0; j < cols; j++) {
                visited[i][j] = 0;
                if (map[i][j] == 'S') {
                    startRow = i;
                    startCol = j;
                } else if (map[i][j] == 'E') {
                    endRow = i;
                    endCol = j;
                } else if (map[i][j] >= '0' && map[i][j] <= '4') {
                    gems[i][j] = map[i][j] - '0';
                } else {
                    gems[i][j] = -1;
                }
            }
        }

        // 计算起始位置到每个宝石的曼哈顿距离
        int gemDistances[5];
        memset(gemDistances, 0, sizeof(gemDistances));
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (gems[i][j] != -1) {
                    int distance = manhattanDistance(startRow, startCol, i, j);
                    if (gemDistances[gems[i][j]] == 0 || distance < gemDistances[gems[i][j]]) {
                        gemDistances[gems[i][j]] = distance;
                    }
                }
            }
        }

        // 根据宝石的曼哈顿距离进行排序
        for (int i = 0; i < requiredGems; i++) {
            int minDistance = -1;
            int minGem = -1;
            for (int j = 0; j < 5; j++) {
                if (gemDistances[j] != 0 && (minDistance == -1 || gemDistances[j] < minDistance)) {
                    minDistance = gemDistances[j];
                    minGem = j;
                }
            }
            gemDistances[minGem] = 0;
            for (int j = 0; j < rows; j++) {
                for (int k = 0; k < cols; k++) {
                    if (gems[j][k] == minGem) {
                        gems[j][k] = i;
                    }
                }
            }
        }

        // 使用贪心算法搜索最短路径
        greedy(startRow, startCol, 0);

        // 输出结果
        if (minTime != -1) {
            printf("%d\n", minTime);
        } else {
            printf("oop!\n");
        }
    }

    return 0;
}

这个代码使用了深度优先搜索和贪心算法来解决问题,首先计算起始位置到每个宝石的曼哈顿距离,然后按照宝石的距离进行排序,然后使用贪心算法进行搜索,直到达到目标位置并且收集到足够数量的宝石。如果无法救出公主,则输出"oop!"。

请注意,这只是一个示例代码,可能还有一些边界情况没有考虑到,你可以根据实际情况进行修改和优化。

相关推荐
Schwertlilien5 分钟前
图像处理-Ch1-数字图像基础
图像处理·人工智能·算法
程序员一诺5 分钟前
【深度学习】嘿马深度学习笔记第10篇:卷积神经网络,学习目标【附代码文档】
人工智能·python·深度学习·算法
过过过呀Glik1 小时前
在 Ubuntu 上安装 Muduo 网络库的详细指南
linux·c++·ubuntu·boost·muduo
刚学HTML2 小时前
leetcode 05 回文字符串
算法·leetcode
蜀黍@猿2 小时前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧4092 小时前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
zh路西法2 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
stm 学习ing2 小时前
HDLBits训练5
c语言·fpga开发·fpga·eda·hdlbits·pld·hdl语言
AC使者2 小时前
#B1630. 数字走向4
算法
冠位观测者2 小时前
【Leetcode 每日一题】2545. 根据第 K 场考试的分数排序
数据结构·算法·leetcode