2023年10月蓝桥杯青少组STEMA考试C++中高级真题解析(第15届)

第一部分 选择题

1、十进制数189转换成二进制是 ( )

A、10111101

B、11010011

C、11110000

D、11001100

【答案】A

【解析】十进制数189转换成二进制的过程如下:

  • 189除以2,商94余1
  • 94除以2,商47余0
  • 47除以2,商23余1
  • 23除以2,商11余1
  • 11除以2,商5余1
  • 5除以2,商2余1
  • 2除以2,商1余0
  • 1除以2,商0余1

将余数从下到上排列得到二进制数:10111101

2、定义一个整型变量int a,使用指针存储a的地址,下列选项正确的是()

A、int &p = &a

B、int **p = &a

C、int *p = a

D、int *p = &a

【答案】D

【解析】

要使用指针存储一个整型变量a的地址,正确的方法是声明一个指向整型的指针,并将它指向变量a的地址。这是通过使用取地址运算符&来获取a的地址,然后将这个地址赋给指针。

选项分析如下:

A. int &p = &a:这是错误的,因为&p是尝试声明一个引用,不是指针,并且类型不匹配。

B. int **p = &a:这是错误的,因为**p表示一个指向指针的指针,而&aa的地址,应该赋值给指向整型的指针。

C. int *p = a:这是错误的,因为p是一个指向整型的指针,而a是一个整型变量。这里缺少了取地址运算符&,所以不能直接将a赋给p

D. int *p = &a:这是正确的,p是一个指向整型的指针,&a是取变量a的地址,所以这个语句将a的地址赋值给指针p

因此,正确答案是D、int *p = &a

3、定义一个字符串数组: char a[1024] =“Welcome to China!":要计算上述字符串的长度,应该使用下列哪个函数()

A、 strcat()

B、 stremp()

C、 strlen()

D、 size()

【答案】C

【解析】计算字符串的长度,应该使用 strlen() 函数。这个函数会返回字符串的长度,不包括终止字符\0

4、己定义变量int a = 10, b = 0:

执行表达式((a = 5) || (b = 1)之后,a和b的值分别是( )

A、10 0

B、5  1

C、5  0

D、10 1

【答案】C

【解析】逻辑或运算符||具有短路特性,即如果第一个操作数的值为真,则不再计算第二个操作数的值。对于表达式((a = 5) || (b = 1))

  1. (a = 5)首先被执行,赋值操作使得a的值变为5,赋值表达式的结果为赋值的值,即5,非零值被视为真。
  2. 由于(a = 5)的结果为真,根据逻辑或||的短路特性,(b = 1)不会被执行。
  3. 因此,a的值变为5,b的值保持不变,即0。

所以,执行表达式((a = 5) || (b = 1))之后,ab的值分别是5和0。

5、运行下面程序,输出的结果是()

int func(int x, int y)

{

      if(x ==1 && y == 1)return 1;

      if(x < 0)return y;

      if(y < 0)return x;

      return (func(x,y-2) + func(x-2,y));

}

int main()

{

      cout<<func(3,3);

      return 0;

}

A、7    B、8    C、9   D、10

【答案】

【解析】

这个函数通过递归调用自身,直到达到基础条件,然后开始逐步返回结果。分析:

  1. 调用func(3,3)
    • 不满足任何基础条件,因此,会进行递归调用:
      • func(3,1):返回func(3,-1) + func(1,1)
        • func(3,-1)返回3(因为y < 0
        • func(1,1)返回1(因为满足x == 1 && y == 1
      • 所以,func(3,1)返回3 + 1 = 4
      • func(1,3):返回func(1,1) + func(-1,3)
        • func(1,1)返回1(因为满足x == 1 && y == 1
        • func(-1,3)返回3(因为x < 0
      • 所以,func(1,3)返回1 + 3 = 4
    • 因此,func(3,3)返回4 + 4 = 8

所以,程序的输出结果是8

通过递归调用,func函数是在计算两个数通过一定规则相结合的结果,当两个数都等于1时返回1,否则会递归地减小它们直到满足基础条件。在这个特定的调用func(3,3)中,最终的结果是8

第二部分 编程题

第一题 编程实现:与3有关的数

提示信息:

如果一个正整数的个位可以被3整除,则这个正整数与3有关

例如:

10的个位是0,0能被3整除,故10与3有关;

11的个位是1,1不能被3整除,故11与3无关

19的个位是9,9能被3整除,故19与3有关

题目描述:

给定一个正整数N(1<=Nm=10000),从小到大依次输出1到N之间所有与3有关的数

输入描述:输入一个正整数 (1<=N<=10000)

输出描述:输出一行,表示1到N之间(含1和N)所有与3有关的正整数,从小到大排列,正整数之间以一个空格隔开

样例输入:10

样例输出:3 6 9 10

【解题思路】

遍历从1到N的所有整数,检查每个数的个位是否能被3整除

【解题步骤】

  1. 遍历所有数字:从1遍历到N,对于每个数字,执行下一步的检查。

  2. 检查个位数:对于每个数字,我们可以通过计算数字除以10的余数来获取其个位数。这是因为余数就是该数字最右侧的数字,即个位数。

  3. 判断个位数是否能被3整除:如果个位数除以3的余数为0,则说明该数字的个位可以被3整除,即该数字与3有关。

  4. 输出与3有关的数:对于每个与3有关的数,将其输出。如果是第一个输出,直接输出该数字;否则,在输出该数字前先输出一个空格,以满足题目中“正整数之间以一个空格隔开”的要求。

【实现代码】

#include <iostream>
using namespace std;

int main() {
    int N;
    cin >> N; // 读取N

    for (int i = 1; i <= N; ++i) {
        if ((i % 10) % 3 == 0 ) { // 检查个位数是否能被3整除
            cout << i << " "; // 输出与3有关的数
        }
    }

    return 0;
}

第二题 编程实现:跳水比赛

题目描述:

某校有N名选手参加跳水比赛。每名选手比赛后,由6名裁判评分,选手的最终得分为6名裁判评分的总和。

比赛结束之后,请计算出最高分与最低分相差多少

例如:N=4,表示有4名选手

裁判对于这4名选手的打分是:

5 9 6 8 7 6

7 7 7 8 6 7

5 7 6 7 5 7

7 7 7 5 9 8

4名选手的最终分数分别为: 41,42,37,43,其中最高分是43分,最低分是37分,那么最高分和最低分相差了6分,故输出6。

输入描述:

第一行输入一个正整数N(2<=N<=100),表示参赛选手的人数

接下来输入N行,每行6个整数(0<=整数<=10),表示每个裁判的评分,整数之间以一个空格隔开

输出描述:

输出一个整数,表示最高分与最低分的差值

样例输入:

4

5 9 6 8 7 6

7 7 7 8 6 7

5 7 6 7 5 7

7 7 7 5 9 8

样例输出:

6

【解题思路】

1. 读入数据

读取选手的数量N,然后创建一个数组存储每个选手的分数总和。接着,通过嵌套循环读取每位选手的6个裁判的评分,计算每位选手的分数总和。

2. 计算分数总和

对于每位选手,将6位裁判的评分加起来得到该选手的总分。这可以通过内层循环实现,遍历每位裁判的评分并累加。

3. 找到最高分和最低分

遍历存储选手分数总和的数组或容器,找到最高分和最低分。这可以通过遍历一次分数数组,同时维护两个变量来记录遍历过程中遇到的最大值和最小值。

4. 计算并输出最高分与最低分的差值

最后,输出最高分和最低分之间的差值。

【代码实现】

#include <iostream>
#include <limits.h> // 用于INT_MAX和INT_MIN
using namespace std;

int main() {
    int N;
    cin >> N; // 读取选手人数

    int maxScore = INT_MIN; // 初始化最高分
    int minScore = INT_MAX; // 初始化最低分

    for (int i = 0; i < N; ++i) {
        int score = 0; // 当前选手的得分
        for (int j = 0; j < 6; ++j) {
            int judgeScore;
            cin >> judgeScore; // 读取裁判的评分
            score += judgeScore; // 累加得分
        }
        if (score > maxScore) { // 更新最高分
            maxScore = score;
        }
        if (score < minScore) { // 更新最低分
            minScore = score;
        }
    }

    cout << maxScore - minScore << endl; // 输出最高分和最低分的差值

    return 0;
}

第三题 编程实现:密文解密

题目描述:

有一段密文,加密规律如下:

原文中所有的字符在字母表中的位置被左移了若干位(如:a左移一位后为z,A左移一位后为Z,f左移三位后为c......)

例如:密文为Ayz,已知加密时原文中的字母被左移了3位,解密后,原文是Dbc。

请根据这个规律,对密文进行解密。

输入描述

共两行

第一行输入一个只包含大小写字母的字符串(长度小于100),表示密文

第二行输入一个正整数n(1<=n<=100000),表示原文中的字母在字母表中的位置被左移的位数

输出描述

输出一个字符串,表示解密后的原文

样例输入:

Ayz

3

样例输出:

Dbc

【解题思路】

  1. 理解加密规律:给定的密文是通过将原文中的每个字母在字母表中向左移动若干位得到的。例如,如果移动3位,那么A变成ZB变成A等等。大小写字母是分开处理的,即大写字母在大写字母表中移动,小写字母在小写字母表中移动。

  2. 解密算法:要解密,需要将每个字母向右移动给定的位数。如果密文是Ayz,并且移动了3位,需要将A变成Dy变成bz变成c

  3. 处理字母表循环:字母表是循环的,即在Z之后是A,在z之后是a。因此,需要在移动字母时考虑这种循环。

  4. 编码实现

    • 对输入的密文字符串的每个字符进行遍历。
    • 根据字符是大写还是小写,计算其在字母表中的新位置。
    • 对新位置的字符进行转换,并拼接到解密字符串上。
    • 输出解密后的字符串。

【代码实现】

#include <iostream>
#include <string>

using namespace std;

// 解密函数
string decrypt(string msg, int shift) {
    string result = ""; // 存储解密结果
    for (char ch : msg) { // 遍历密文中的每个字符
        if (isalpha(ch)) { // 如果字符是字母
            char base = isupper(ch) ? 'A' : 'a'; // 确定是大写还是小写
            int pos = (ch - base + 26 - (shift % 26)) % 26; // 计算解密后的位置
            result += base + pos; // 添加到结果字符串
        } else {
            result += ch; // 非字母字符直接添加
        }
    }
    return result; // 返回解密字符串
}

int main() {
    string msg; // 密文
    int n; // 移位数
    getline(cin, msg); // 输入密文
    cin >> n; // 输入移位数
    cout << decrypt(msg, n) << endl; // 输出解密后的原文
    return 0;
}

【考察知识点】

  1. 字符串处理:如何遍历和操作字符串中的每个字符,包括识别字符类型(大写字母、小写字母)。

  2. 字符编码:理解字符(特别是字母)在计算机中是如何通过ASCII码或其他编码系统表示的,并利用这些编码规则进行字符之间的转换和移动。

  3. 模运算的应用:利用模(%)运算处理字母表的循环。当字母移动到Z之后或z之后,它应该循环回到Aa,这里模运算是实现这种循环的关键。

  4. 条件判断和循环:使用条件判断来区分处理大写字母和小写字母,以及使用循环遍历字符串中的每个字符。

  5. 算法思维:如何根据加密规则设计出相应的解密算法。这包括了逆向思维的应用,即理解和实现从密文回到原文的过程。

  6. 基本的输入输出操作:读取输入(包括字符串和整数)和输出处理结果。

第四题 编程实现:翻转游戏币

题目描述:

桌面上有n枚游戏币,均为反面朝上,编号依次为1到n。有n个人游戏币的数量与人的数量相等),首先第1个人将所有游戏币翻转,然后第2个人将所有编号是2的倍数的游戏币翻转,接下来第3个人将所有编号是3的倍数的游戏币翻转…以此类推,当最后一个人完成操作后,还有多少枚游戏币正面朝上?

例如:n=4

最初4枚游戏币的状态为:反反反反;经过第1个人翻转后,游戏币的状态为:正正正正;

经过第2个人翻转后,游戏币的状态为:正反正反

经过第3个人翻转后,游戏币的状态为:正反反反;

经过第4个人翻转后,游戏币的状态为:正反反正最后,还有两枚游戏币正面朝上,故答案为2

输入描述:

输入一个正整数n(3≤n≤10),表示游戏币数量及人的数量输出描述:

输出一个整数,表示最终游戏币正面朝上的数量

样例输入:

4

样例输出:

2

【解题思路】

模拟过程

  1. 初始化游戏币状态:创建一个数组来表示所有游戏币的状态,初始时都设为反面朝上。
  2. 按规则翻转游戏币:按照题目描述的规则,对于每个人(编号从1到n),遍历游戏币,如果游戏币的编号是该人编号的倍数,则翻转游戏币的状态。
  3. 计数正面朝上的游戏币:遍历所有游戏币,计数状态为正面朝上的游戏币数量。

【实现代码】

#include <iostream>

using namespace std;

const int MAX_N = 100; // 假设n的最大值为100
bool coins[MAX_N + 1]; // 声明一个全局数组,所有元素默认为false,代表反面朝上

int main() {
    int n;
    cin >> n; // 输入游戏币的数量

    // 初始化游戏币状态为反面朝上
    for (int i = 1; i <= n; ++i) {
        coins[i] = false;
    }

    // 模拟翻转过程
    for (int i = 1; i <= n; ++i) { // 对于每个人
        for (int j = i; j <= n; j += i) { // 遍历游戏币,此处进行了优化,直接跳过非倍数的游戏币
            coins[j] = !coins[j]; // 翻转游戏币状态
        }
    }

    // 计数正面朝上的游戏币数量
    int count = 0;
    for (int i = 1; i <= n; ++i) {
        if (coins[i]) { // 如果游戏币是正面朝上
            ++count;
        }
    }

    cout << count << endl; // 输出正面朝上的游戏币数量

    return 0;
}

第五题 编程实现:分发糖果

题目描述:

n个学生站成一排,已知每名学生的考试成绩,老师要根据成绩按以下规则分发糖果

1、每个学生至少得到一个糖果;

2、相邻两个学生中成绩高的会获得更多的糖果

3、相邻两个学生成绩即使相同,获得的糖果数量也可以不同

请计算出老师最少需要准备多少颗糖果?

例如:有3个学生,他们的考试成绩分别是70,50,80,可以给第一个学生2颗糖果,给第二个学生1颗糖果,给第三个学生2颗糖果,所以最少需要准备5颗糖果。

输入描述

共两行

第一行输入一个正整数n(1≤n≤20000),表示学生人数第二行输入n个整数(0≤整数≤100),表示每个学生的考试成绩,整数之间以一个空格隔开输出描述:

输出一个整数,表示最少需要准备的糖果数量

样例输入:

3

70 50 80

样例输出:

5

【解题思路】

这个问题的思路涉及到两遍扫描法,旨在满足题目中的两个主要条件:每个学生至少得到一个糖果,且成绩高的学生比相邻的成绩低的学生得到更多的糖果。不需要关心成绩相同学生之间的糖果分配情况,这简化了问题。

第一步:初始化糖果分配

  • 首先,为每个学生分配1颗糖果,以满足“每个学生至少得到一个糖果”的规则。

第二步:从左到右扫描

  • 然后,从左到右遍历学生的成绩数组。对于每一对相邻的学生,如果右边的学生成绩高于左边的学生,那么右边的学生应该比左边的学生多得到1颗糖果。这一步保证了从左往右看时,每个学生都满足规则。

第三步:从右到左扫描

  • 接下来,从右到左遍历学生的成绩数组。这一次,如果左边的学生成绩高于右边的学生,并且左边的学生目前得到的糖果不多于右边的学生,那么左边的学生应该比右边的学生多得到1颗糖果。这一步确保了从右往左看时,每个学生也满足规则。

第四步:计算总糖果数

  • 最后,将所有学生得到的糖果数加起来,得到的总和就是老师需要准备的最少糖果数。

【实现代码】

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int n;
    cin >> n; // 输入学生数量

    vector<int> scores(n);
    for (int i = 0; i < n; i++) {
        cin >> scores[i]; // 输入每个学生的成绩
    }

    vector<int> candies(n, 1); // 每个学生最少获得1颗糖果

    // 从左向右扫描
    for (int i = 1; i < n; i++) {
        if (scores[i] > scores[i - 1]) {
            candies[i] = candies[i - 1] + 1;
        }
    }

    // 从右向左扫描
    for (int i = n - 2; i >= 0; i--) {
        if (scores[i] > scores[i + 1]) {
            candies[i] = max(candies[i], candies[i + 1] + 1);
        }
    }

    // 计算总糖果数量
    int totalCandies = 0;
    for (int i = 0; i < n; i++) {
        totalCandies += candies[i];
    }

    cout << totalCandies << endl; // 输出最少需要的糖果数量

    return 0;
}

【代码原理】

实现的原理基于贪心算法,贪心算法在每一步选择中都采取当前状态下的最优解(局部最优解),以达到全局最优的结果。在这个特定问题中,贪心算法通过两遍扫描来确保每个学生都能按照成绩分配到合理的糖果数量,同时使得总糖果数量达到最小。下面详细解释这个原理:

第一遍扫描(从左到右)

  • 目的:确保每个学生相对于他左边的学生获得合理的糖果数量。如果某个学生的成绩比他左边的学生高,那么他应该获得比左边学生更多的糖果。
  • 方法:遍历成绩数组,每次比较当前学生和他左边学生的成绩。如果当前学生成绩高,就给当前学生分配的糖果数为左边学生糖果数加一。

第二遍扫描(从右到左)

  • 目的:修正每个学生相对于他右边的学生的糖果数量。在第一遍扫描之后,有些学生可能因为他们右侧的学生有更高的成绩而需要更多的糖果来满足条件。
  • 方法:逆向遍历成绩数组,这次比较当前学生和他右边学生的成绩。如果当前学生成绩高,且当前分配的糖果数不多于右边学生,就更新当前学生的糖果数为右边学生糖果数加一。

为何需要两遍扫描

  • 在第一遍扫描结束后,我们确保了每个学生相对于他左侧的学生获得了合理的糖果数。但是,这可能会导致一些右侧有成绩更高的学生没有得到比左侧成绩低的学生更多的糖果。
  • 第二遍扫描是必要的,因为它修正了这一问题,确保了无论从左侧还是右侧查看,成绩较高的学生总是获得更多的糖果。

计算总糖果数

  • 最后,通过累加每个学生分配到的糖果数,得到了满足所有条件下老师需要准备的最少糖果总数。

通过这种方法,代码不仅保证了每个学生至少得到一个糖果,而且保证了成绩更高的学生相对于他们相邻的学生得到更多的糖果,同时又使得总糖果数尽可能少,达到了一个全局最优解。

第六题 编程实现:区间最小值

题目描述:

给定n个整数,从1到n顺序编号,接下来进行m次查询,第i次查询第ai个数到第bi个数(包含ai和bi:)之间的最小值并输出。

例如:n =8,8个正整数依次为: 40 20 10 30 70 50 80 60

m= 3,3次查询分别为:

a1 = 3,b1 = 7

a2 = 1,b2 = 2

a3 = 5,b3 = 8

第一次查询:第3个数(10)到第7个数 (80)之间最小值是10:

第二次查询:第1个数(40)到第2个数(20)之间最小值是20;

第三次查询:第5个数(70)到第8个数 (60)之间最小值是50;

故输出

10

20

50.

输入描述

第一行输入两个整数n和m(1≤n,m≤108),分别表示整数的数量及查询次数

第二行输入n个整数(0≤整数≤105)

接下来m行,每行输入2个整数a和:(1≤ai≤bi≤n),分别表示查询的起始位置和终止位置

输出描述:

输出共m行,每行输出一个整数,分别表示每次查询得到的第ai个数到第bi个数之间(包含ai和bi)的最小值

样例输入:

8 3

40 20 10 30 70 50 80 60

3 7

1 2

5 8

样例输出:

10

20

50

【解题思路】

可以通过以下几种思路来实现:

1. 暴力解法

对于每次查询,直接在指定的区间内遍历所有整数,寻找并输出最小值。这种方法简单直接,但当nm很大时,效率较低,因为它的时间复杂度为O(m*n),即对于每次查询都需要遍历整个区间。

2. 分而治之:分段预处理

一种更高效的方法是对原始的整数序列进行预处理,分段存储每个区间的最小值。例如,可以预先计算出所有从第i个数开始的长度为1, 2, 4, 8, ... 的区间的最小值,并存储起来。这样,每次查询时,可以将查询区间分解为几个预处理过的区间的组合,从而快速找到整个查询区间的最小值。这种方法需要的预处理时间和空间都较大,但查询效率很高,适用于“少修改、多查询”的情况。

3. 线段树

线段树是一种高效处理一系列区间查询问题的数据结构。通过构建一棵线段树,可以在O(log n)的时间复杂度内查询任意区间的最小值(或其他区间统计量),同时也能在O(log n)时间内更新序列中的元素。构建线段树的时间复杂度为O(n),适合于查询次数远大于序列修改次数的情况。

4. 树状数组

树状数组或二叉索引树也可以用来处理这类问题,但它主要适用于区间的累加查询和单点更新,对于区间最小值查询,线段树是更合适的选择。

【代码实现】

暴力解法

#include<iostream>
using namespace std;

const int N=10000; // 定义一个常量N作为数组的最大大小

int w[N],a[N],b[N]; // 定义三个数组:w存储每个学生的分数,a和b存储查询的起始和结束位置

int main () {
    int n,m; // n表示整数的数量,m表示查询的次数
    cin>>n>>m; // 读入n和m的值

    // 读入每个学生的分数,存储到数组w中
    for(int i=1;i<=n;i++)
        cin>>w[i];
        
    // 读入每次查询的起始和结束位置,存储到数组a和b中
    for(int i=1;i<=m;i++)
        cin>>a[i]>>b[i];
        
    // 遍历每一次查询
    for(int i=1;i<=m;i++)
    {
        int min1=10000; // 初始化最小值变量min1为一个很大的数,以便在接下来的查找中被替换
        // 遍历当前查询的起始位置到结束位置的所有整数
        for(int j=a[i];j<=b[i];j++)
        {
            // 如果当前元素的值小于已记录的最小值,更新最小值
            if(min1>w[j])
                min1=w[j];    
        }
        // 输出当前查询区间内的最小值
        cout<<min1<<endl;
    }    
}

【代码步骤】

初始化和输入

  • 首先,通过定义足够大的常量N和相应的数组wab来准备存储空间。N代表了数组的最大可能大小,w用于存储整数序列,ab用于存储查询的起始和结束位置。
  • 接着,程序读取两个输入:整数的数量n和查询的次数m
  • 然后,通过循环读取n个整数,填充到数组w中,这些整数构成了待查询的序列。
  • 通过另一个循环读取每次查询的起始位置a[i]和结束位置b[i],为接下来的查询做准备。

查询处理

  • 对于每一次查询(共m次),程序先初始化一个变量min1,将其设置为一个较大的值(这里设为10000),以确保任何序列中的数都不会比这个值大。这个变量用于在接下来的搜索中存储找到的最小值。
  • 然后,程序遍历从a[i]b[i]的每个元素(即每次查询指定的区间内的所有整数),使用条件语句检查当前遍历到的元素w[j]是否小于当前记录的最小值min1。如果是,就更新min1为这个更小的值。
  • 遍历完指定区间后,min1中存储的就是这个区间内的最小值。最后,程序输出这个最小值。

总结

这个过程对每一次查询都独立执行,不利用任何先前查询的结果,也不进行任何预处理或优化。因此,其效率随着查询次数m和每次查询区间的平均长度的增加而线性下降。对于小到中等规模的问题,这种方法是有效的;但对于大规模数据,或者要求高效率处理大量查询的场景,需要采用更高效的数据结构和算法,如线段树或者树状数组,来优化查询性能。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值