第十五届蓝桥杯青少组C++中级组省赛真题+答案解析
一、选择题
1. 定义 char a[]="hello\nworld"
,执行 cout<<a
,输出结果是( )。
- A.
helloworld
- B.
hello
world
- C.
hellonworld
- D.
hello\nworld
答案:B
解析:在C++中,\n
是转义字符,表示换行。当执行cout<<a
时,遇到\n
会进行换行操作,所以输出为hello
换行后接着world
。
2. (11001010)₂ + (F8)₁₆
的结果是( )。
- A.
(11001010)₂
- B.
(701)₈
- C.
(1C2)₁₆
- D.
(452)₁₀
答案:C
解析:首先将二进制数(11001010)₂
转换为十六进制。从右到左每4位二进制数一组进行转换,1100
对应十六进制的C
,1010
对应十六进制的A
,所以(11001010)₂=(CA)₁₆
。然后进行十六进制加法(CA)₁₆ + (F8)₁₆
,按照十六进制加法规则,个位A + 8 = 12
,十六进制中用C
表示,向高位进1 ;十位C + F + 1 = 1C
,所以结果为(1C2)₁₆
。
3. 表达式 4%12
的结果是( )。
- A. 0
- B. 4
- C. 3
- D. 12
答案:B
解析:%
是取余运算符,其作用是求两个整数相除后的余数。在4%12
中,4除以12,商为0,余数为4,所以结果是4。
4. 下列选项中,逻辑表达式的值始终与 B 的真假无关的是( )。
- A.
(!A || B) && A
- B.
(A || B) && (!A && B)
- C.
(A && !A)||B
- D.
(A || B) && (A || !B)
答案:D
解析:A. (!A || B) && A,当 A=0,整个表达式一定为假;当 A=1,表达式值依赖于 B,因此结果与 B 有关。
B. (A || B) && (!A && B),A=1时,右侧为假 → 整体为假;A=0时,结果依赖于 B → 与 B 有关。
C. (A && !A) || B,(A && !A)恒为假,表达式为 B → 与 B 有关。
D. (A || B) && (A || !B),A=0时:(A || B)=B,(A || !B)=!B;B && !B 为假,与B无关 ;A=1时,左边为真,右边为真,整体为真,与B无关,故选D选项。
5. 运行下面程序,输出结果是( )。
int a[6] = {16, 8, 32, 10, 9, 21};
int func(int L, int R, int d)
{
if(L > R)
return 0;
int sum = 0, m = a[L], index = L;
for(int i = L + 1; i <= R; i++)
{
if(m < a[i])
{
m = a[i];
index = i;
}
}
int lt = func(L, index - 1, d + 1);
int rt = func(index + 1, R, d + 1);
return lt + rt + d * m;
}
int main()
{
cout << func(0, 5, 1);
return 0;
}
- A. 196
- B. 197
- C. 198
- D. 199
答案:A
解析:func
函数是一个递归函数,功能是在数组a
的[L, R]
区间内寻找最大值m
及其下标index
。然后递归地计算[L, index - 1]
和[index + 1, R]
这两个子区间的结果,并加上当前最大值m
与递归深度d
的乘积。具体计算过程如下:
- 初始调用
func(0, 5, 1)
:- 在
[0, 5]
区间内,最大值为32
(下标为2),所以lt = func(0, 1, 2)
,rt = func(3, 5, 2)
,d = 1
,m = 32
,此时结果为func(0, 1, 2) + func(3, 5, 2) + 1 * 32
。
- 在
- 计算
func(0, 1, 2)
:- 在
[0, 1]
区间内,最大值为16
(下标为0),所以lt = func(0, -1, 3) = 0
(因为L > R
返回0),rt = func(1, 1, 3)
,d = 2
,m = 16
,此时结果为0 + func(1, 1, 3) + 2 * 16
。 - 计算
func(1, 1, 3)
:在[1, 1]
区间内,最大值为8
(下标为1),lt = func(1, 0, 4) = 0
,rt = func(2, 1, 4) = 0
,d = 3
,m = 8
,结果为0 + 0 + 3 * 8
。所以func(0, 1, 2) = 0 + 0 + 3 * 8 + 2 * 16 = 56
。
- 在
- 计算
func(3, 5, 2)
:- 在
[3, 5]
区间内,最大值为21
(下标为5),所以lt = func(3, 4, 3)
,rt = func(6, 5, 3) = 0
(因为L > R
返回0),d = 2
,m = 21
,此时结果为func(3, 4, 3) + 0 + 2 * 21
。 - 计算
func(3, 4, 3)
:在[3, 4]
区间内,最大值为10
(下标为3),lt = func(3, 2, 4) = 0
,rt = func(4, 4, 4)
,d = 3
,m = 10
,结果为0 + func(4, 4, 4) + 3 * 10
。 - 计算
func(4, 4, 4)
:在[4, 4]
区间内,最大值为9
(下标为4),lt = func(4, 3, 5) = 0
,rt = func(5, 4, 5) = 0
,d = 4
,m = 9
,结果为0 + 0 + 4 * 9
。所以func(3, 4, 3) = 0 + 0 + 4 * 9 + 3 * 10 = 66
。
- 在
- 最终
func(0, 5, 1) = 56 + 66 + 32 = 196
。
二、编程题
1. 看书
题目描述:一本书共 n
页,小明计划第一天看 x
页,此后每一天都要比前一天多看 y
页,请问小明几天可以看完这本书?
输入格式:一行输入三个整数 n, x, y
(20 <= n <= 5000, 1 < x, y <= 20
),分别表示书的总页数、计划第一天看的页数以及此后每天都要比前一天多看的页数,整数之间以一个空格隔开。
输出格式:输出一个整数,表示小明几天可以看完这本书。
输入样例:
100 10 5
输出样例:
5
题目解析:本题核心是通过模拟小明每天看书页数的累加过程来确定看完书所需天数。已知书的总页数 n
,以及小明每天看书页数的变化规律(第一天看 x
页,之后每天比前一天多看 y
页),我们需要不断累加每天看的页数,直到累加和大于或等于总页数 n
,此时累加的次数就是看完书需要的天数。
解题步骤
- 数据输入:从输入中获取书的总页数
n
、第一天看的页数x
和每天比前一天多看的页数y
。 - 初始化变量:定义变量
sum
用于记录小明总共看的页数,初始化为0
;定义变量cnt
用于统计天数,初始化为0
。 - 循环累加:使用
while
循环,在循环内,每次让天数cnt
增加1
,同时将当天看的页数x
累加到sum
中,并且更新下一天要看的页数x
(即x = x + y
)。每次循环检查sum
是否大于或等于n
,如果满足条件则跳出循环。 - 输出结果:循环结束后,输出
cnt
,它就是小明看完书需要的天数。
代码实现
#include <iostream>
using namespace std;
int main() {
int n, x, y;
// 从输入获取书的总页数n、第一天看的页数x和每天比前一天多看的页数y
cin >> n >> x >> y;
int sum = 0;
// sum用于记录小明总共看的页数,初始化为0
int cnt = 0;
// cnt用于统计天数,初始化为0
while (true) {
cnt++;
// 天数增加1
sum += x;
// 将当天看的页数x累加到sum中
x += y;
// 更新下一天要看的页数,即比前一天多看y页
if (sum >= n) {
// 如果总共看的页数大于等于书的总页数n
break;
// 跳出循环
}
}
cout << cnt;
// 输出看完书需要的天数
return 0;
}
知识点总结
- 变量定义与输入输出:学会定义不同类型的变量来存储数据,并使用
cin
和cout
进行数据的输入与输出操作。 - 循环结构:通过
while
循环实现对小明看书过程的模拟,理解循环条件和循环体的执行逻辑,掌握如何通过循环来解决需要重复执行某些操作的问题。 - 算术运算:在程序中运用加法运算(
+=
)来累加总页数和更新每天看书的页数,加深对算术运算符的理解和运用。
2. 数字交换
题目描述:前导 0 是指整数前面那些对数值无影响的 0。例如,0201
去除前导 0 后为 201
;00321
去除前导 0 后为 321
。给定一个正整数 n
,请将 n
的最高位与最低位的数字进行交换,并输出交换后的结果。如果交换后的结果有前导 0,去除前导 0 后再输出结果。例如,当 n = 173
时,将 173 的最高位 1 与最低位 3 交换,交换后的结果为 371
;当 n = 10200
时,将 10200 的最高位 1 与最低位 0 交换,交换后的结果为 00201
,去除前导 0 后的结果为 201
。
输入格式:输入一个正整数 n
(100 ≤ n ≤ 10⁹
)。
输出格式:输出一个整数,表示将 n
的最高位与最低位的数字交换后的结果。如果交换后的结果有前导 0,去除前导 0 后再输出结果。
输入样例:
173
输出样例:
371
题目解析:本题需要对输入的正整数进行数位操作。由于直接对整数进行数位交换较为复杂,我们借助字符串来处理。将整数转换为字符串后,可以方便地通过索引访问每个字符(对应整数的每个数位)。先交换字符串的第一个字符(最高位)和最后一个字符(最低位),然后处理可能出现的前导 0 问题,即找到从左数第一个非 0 字符,并输出从该字符开始的子串。
解题步骤
- 输入并转换:将输入的正整数
n
以字符串形式读入,方便后续操作。 - 字符交换:使用
swap
函数交换字符串的第一个字符s[0]
和最后一个字符s[s.size() - 1]
,实现最高位与最低位数字的交换。 - 处理前导 0:从字符串的起始位置开始遍历,找到第一个非 0 字符的位置
p
。 - 输出结果:输出从位置
p
开始的子串,即为交换并去除前导 0 后的结果。
代码实现
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
// 以字符串形式读入正整数n
cin >> s;
// 交换字符串s的第一个字符(最高位)和最后一个字符(最低位)
swap(s[0], s[s.size() - 1]);
int p = 0;
// 从字符串起始位置开始遍历,找到第一个非0字符的位置p
while (s[p] == '0') {
p++;
}
// 输出从位置p开始的子串,即交换并去除前导0后的结果
cout << s.substr(p);
return 0;
}
知识点总结
- 字符串操作:学会使用
string
类型来处理文本数据,掌握字符串的输入、通过索引访问字符、交换字符以及获取子串(substr
)等操作。 - 循环与条件判断:通过
while
循环结合条件判断(s[p] == '0'
)来找到第一个非 0 字符的位置,理解循环和条件判断语句在解决实际问题中的应用。 - 函数调用:使用标准库函数
swap
来交换字符串中的两个字符,体会函数在简化代码和提高代码可读性方面的作用。
3. 出现奇数次的数
题目描述:奇数是指不能被 2 整除的整数,例如 3、5 是奇数,4、6 不是奇数。给定 n
个整数,其中只有一个数出现了奇数次,请找出这个数。例如,对于 7 个整数 6、2、4、6、4、2、6,其中只有 6 出现了奇数次,所以输出 6。
输入格式:第一行输入一个整数 n
(1 ≤ n ≤ 10⁵
);第二行输入 n
个整数(1 ≤ 整数 ≤ 10⁹
),整数之间以一个空格隔开,数据保证只有一个数出现了奇数次。
输出格式:输出一个整数,表示出现了奇数次的数。
输入样例:
7
6 2 4 6 4 2 6
输出样例:
6
题目解析:本题要求从给定的 n
个整数中找出唯一出现奇数次的数。采用排序后遍历统计的思路,先对这 n
个整数进行排序,相同的数会相邻排列。然后遍历排序后的数组,通过统计每个数出现的次数来找出出现奇数次的数。
解题步骤
- 数据输入:首先输入整数的个数
n
,然后通过循环输入n
个整数,并将它们存储到数组a
中。 - 数组排序:使用
sort
函数对数组a
进行排序,使相同的数相邻,方便后续统计次数。 - 遍历统计:定义变量
cnt
用于统计当前数出现的次数,初始化为1
。遍历数组a
,比较当前数a[i]
与下一个数a[i + 1]
。如果不相等,检查cnt
是否为奇数,若是则输出a[i]
并结束程序;若cnt
为偶数,则将cnt
重置为1
。如果a[i]
与a[i + 1]
相等,则cnt
自增1
。 - 输出结果:找到出现奇数次的数后,输出该数。
代码实现
#include <iostream>
#include <algorithm>
using namespace std;
int a[100005];
int main() {
int n;
// 输入整数的个数n
cin >> n;
for (int i = 1; i <= n; i++) {
// 循环输入n个整数并存储到数组a中
cin >> a[i];
}
// 对数组a进行排序,使相同的数相邻
sort(a + 1, a + n + 1);
int cnt = 1;
// cnt用于统计当前数出现的次数,初始化为1
for (int i = 1; i <= n; i++) {
if (a[i] != a[i + 1]) {
// 如果当前数与下一个数不相等
if (cnt % 2 == 1) {
// 如果当前数出现次数为奇数
cout << a[i];
// 输出该数
break;
// 结束程序
}
cnt = 1;
// 重置出现次数为1,准备统计下一个数的出现次数
}
else {
cnt++;
// 当前数与下一个数相等,出现次数加1
}
}
return 0;
}
知识点总结
- 数组操作:学会定义数组来存储多个数据,掌握数组的输入和排序操作。这里使用
sort
函数对数组进行排序,了解标准库函数在数组处理中的应用。 - 循环与条件判断:通过
for
循环遍历数组,结合条件判断语句(if - else
)来统计每个数出现的次数,并根据次数的奇偶性决定输出结果,深入理解循环和条件判断的嵌套使用。 - 变量的作用域和生命周期:理解变量
cnt
在程序中的作用域和生命周期,它在统计每个数出现次数的过程中起到关键作用,并且其值随着循环的进行而动态变化。
4. 字母移位
题目描述:字母移位指将字母按照字母表顺序移动。例如,b
向右移动一位是 c
,f
向左移动两位是 d
,特别地,a
向左移动一位是 z
,z
向右移动一位是 a
。给定一个仅包含小写字母且长度为 n
的字符串 s
,以及 n
个正整数 a₁, a₂, ……, aₙ
。对字符串 s
按如下规律操作:
- 将第 1 位字符向左移动
a₁
位; - 再将第 1、2 位字符都向右移动
a₂
位; - 再将第 1、2、3 位字符都向左移动
a₃
位; - 再将第 1、2、3、4 位字符都向右移动
a₄
位;
……
(依此类推,第k
步操作:将前k
位字符按方向移动aₖ
位,方向为:第奇数步(1、3、5…)向左移动,第偶数步(2、4、6…)向右移动 )。最后输出操作完成后的字符串。
输入格式:
- 第一行输入一个整数
n
(1≤n≤10^5
)。 - 第二行输入一个仅包含小写字母且长度为
n
的字符串s
。 - 第三行输入
n
个整数a₁, a₂, ……, aₙ
(1≤aᵢ≤10^9
),整数之间以一个空格隔开。
输出格式:输出操作完成后的字符串。
输入样例:
5
abcde
1 3 5 7 9
输出样例:
vxvbv
题目解析:本题需要按照特定规则对字符串中的字符进行移位操作。每一步操作针对前 k
个字符,且移动方向根据步骤的奇偶性决定,同时要处理好字母在字母表中的循环移位(如 a
左移到 z
,z
右移到 a
)。
解题步骤:
- 输入数据:读取整数
n
,字符串s
以及整数数组a
。 - 按步骤操作:通过循环从第 1 步到第
n
步进行操作。- 确定当前步骤的移动方向
dir
,奇数步为向左(-1
),偶数步为向右(1
)。 - 获取当前步骤的移动步数
step
即a[k - 1]
。 - 对前
k
个字符进行移位操作,计算每个字符移动后的位置,处理循环移位情况,更新字符串s
中相应位置的字符。
- 确定当前步骤的移动方向
- 输出结果:输出操作完成后的字符串
s
。
代码实现:
#include <iostream>
#include <string>
using namespace std;
int main() {
int n;
string s;
// 读取整数n和字符串s
cin >> n >> s;
int a[100005];
for (int i = 0; i < n; i++) {
// 读取n个整数存储到数组a中
cin >> a[i];
}
for (int k = 1; k <= n; k++) {
// 确定当前步骤的移动方向,奇数步向左(-1),偶数步向右(1)
int dir = (k % 2 == 1)? -1 : 1;
int step = a[k - 1];
// 获取当前步骤的移动步数
for (int i = 0; i < k; i++) {
int delta = dir * step;
// 计算字符移动的偏移量
char c = s[i];
// 获取当前要移动的字符
int pos = c - 'a';
// 将字符转换为0 - 25的整数表示其在字母表中的位置
pos = (pos + delta) % 26;
// 计算移动后的位置,对26取模保证在字母表范围内
if (pos < 0) pos += 26;
// 如果移动后位置为负数,调整为正数
s[i] = 'a' + pos;
// 更新字符串中该位置的字符
}
}
cout << s << endl;
// 输出操作完成后的字符串
return 0;
}
知识点总结:
- 字符串操作:学会对字符串中单个字符的访问和修改,通过索引遍历字符串。
- 循环结构:利用
for
循环控制操作步骤以及对每个步骤中字符的处理,理解嵌套循环在解决复杂问题中的应用。 - 条件判断:根据步骤的奇偶性确定移动方向,通过
if - else
或条件运算符(? :
)实现条件判断。 - 字符与整数的转换:将字符转换为整数进行移位计算,再将计算后的整数转换回字符,理解字符在计算机中的存储和编码方式。
5. 通关游戏的最少能量值
题目描述:有一款游戏,通关需完成 n
个任务,任务可按任意次序完成。每个任务设有启动能量值和完成任务消耗的能量值,且消耗能量值小于等于启动能量值,玩家当前能量值低于启动能量值则无法开始任务。例如,玩家当前能量值为 7,任务启动能量值为 5,完成任务消耗能量值为 3,则可开始任务,完成后剩余能量值为 4;若玩家当前能量值为 5,任务启动能量值为 8,则无法开始任务。游戏开始时玩家需一个初始能量值完成 n
个任务,给定每个任务的启动能量值和完成任务消耗的能量值,求初始能量的最小值。
输入格式:
- 共
n + 1
行。 - 第一行输入一个整数
n
(1≤n≤10^5
),表示任务数量。 - 接下来
n
行,每行输入两个整数x
,y
(1≤y≤x≤1000
),分别表示当前任务所需的启动能量值和完成任务所消耗的能量值,整数之间以一个空格隔开。
输出格式:输出一个整数,表示玩家完成 n
个任务需要的初始能量的最小值。
输入样例:
3
2 2
9 5
7 4
输出样例:
12
题目解析:本题可采用贪心策略。按“所需启动能量值 - 消耗能量值”升序排序任务,遍历每个任务并调整初始能量,使初始能量正好能完成每个任务,最终完成任务的顺序是按“所需启动能量值 - 消耗能量值”降序。
解题步骤:
- 定义结构体和输入数据:定义结构体
Node
存储每个任务的启动能量值x
和消耗能量值y
,输入任务数量n
以及每个任务的x
和y
值。 - 排序任务:按“所需启动能量值 - 消耗能量值”升序对任务数组进行排序。
- 计算初始能量:初始化初始能量
sum
为第一个任务的启动能量值a[1].x
。遍历剩余任务,如果当前任务的“所需启动能量值 - 消耗能量值”小于当前累计能量sum
,则将当前任务的消耗能量值加到sum
上;否则,将sum
更新为当前任务的启动能量值。 - 输出结果:输出计算得到的初始能量最小值
sum
。
代码实现:
#include <iostream>
#include <algorithm>
using namespace std;
struct Node {
int x;
// 存储任务的启动能量值
int y;
// 存储任务的消耗能量值
};
Node a[100005];
// 自定义比较函数,按“所需启动能量值 - 消耗能量值”升序排序
bool cmp(Node t1, Node t2) {
return t1.x - t1.y < t2.x - t2.y;
}
int main() {
int n;
// 输入任务数量n
cin >> n;
for (int i = 1; i <= n; i++) {
// 循环输入每个任务的启动能量值x和消耗能量值y
cin >> a[i].x >> a[i].y;
}
// 按自定义比较函数对任务数组进行排序
sort(a + 1, a + n + 1, cmp);
long long sum = a[1].x;
// 初始化初始能量为第一个任务的启动能量值
for (int i = 2; i <= n; i++) {
if (a[i].x - a[i].y < sum)
// 如果当前任务的“所需启动能量值 - 消耗能量值”小于当前累计能量
sum += a[i].y;
// 将当前任务的消耗能量值加到sum上
else
sum = a[i].x;
// 否则,将sum更新为当前任务的启动能量值
}
cout << sum;
// 输出计算得到的初始能量最小值
return 0;
}
知识点总结:
- 结构体定义与使用:学会定义结构体来存储相关联的数据,如任务的启动能量值和消耗能量值。
- 排序算法:使用
sort
函数和自定义比较函数对结构体数组进行排序,理解排序在贪心算法中的应用。 - 贪心算法思想:掌握贪心算法的基本思想,通过局部最优选择达到全局最优解,在本题中按特定规则选择任务以确定最小初始能量。
6. 物品分组
题目描述:有 n
件物品排成一排,编号为 1 到 n
,价值分别为 a₁, a₂, ……, aₙ
。要将这 n
件物品拆分成 k
组(不改变物品顺序),每组至少有一件物品,统计每组物品价值之和并找出其中的最大值。设计一种分组方案,使这个最大值尽可能小,并输出这个最小值。
输入格式:
- 第一行输入一个整数
n
(1≤n≤1000
),表示物品数量。 - 第二行输入
n
个整数a₁, a₂, ……, aₙ
(1 ≤ aᵢ ≤ 10^5
),表示每件物品的价值,整数之间以一个空格隔开。 - 第三行输入一个整数
k
(1≤k≤n
),表示要拆分的组数。
输出格式:输出一个整数,表示按照题目要求得到的最大值。
输入样例:
5
6 1 3 8 4
2
输出样例:
12
题目解析:本题可使用二分答案的方法。设每组和的最大值为 m
,则 m
不小于 a₁, a₂, ……, aₙ
中的最大值 maxn
,不大于 a₁, a₂, ……, aₙ
的和 sum
。在区间 (maxn
, sum
) 中二分查找答案,找到分组数不大于 k
的最大答案。
解题步骤:
- 输入数据并初始化:输入物品数量
n
,每件物品的价值数组a
和组数k
。计算价值总和sum
和最大值maxn
。 - 二分查找:在区间 (
maxn
,sum
) 中进行二分查找。- 计算中间值
mid
。 - 通过
count
函数统计以mid
为每组和最大值时能分成的组数。 - 如果分组数小于等于
k
,说明mid
可能是答案,更新答案ans
并缩小右边界r = mid - 1
;否则,增大左边界l = mid + 1
。
- 计算中间值
- 输出结果:输出最终找到的答案
ans
。
代码实现:
#include <iostream>
#include <algorithm>
using namespace std;
int n, k;
int a[1005];
// 统计以m为每组和最大值时能分成的组数
int count(int m) {
int cnt = 1;
// 初始组数为1
int sum = 0;
// 每组的和初始化为0
for (int i = 1; i <= n; i++) {
if (sum + a[i] > m) {
// 如果当前组的和加上物品i的价值大于m
cnt++;
// 组数增加1
sum = a[i];
// 开始新的一组,和为物品i的价值
}
else {
sum += a[i];
// 否则将物品i的价值加入当前组
}
}
return cnt;
// 返回分组数
}
int main() {
cin >> n;
int sum = 0;
// 初始化物品价值总和为0
int maxn = -1e9;
// 初始化物品价值最大值为极小值
for (int i = 1; i <= n; i++) {
cin >> a[i];
maxn = max(maxn, a[i]);
// 更新物品价值最大值
sum += a[i];
// 累加物品价值总和
}
cin >> k;
int l = maxn, r = sum;
// 二分查找区间,左边界为最大值,右边界为总和
int ans;
while (l <= r) {
int mid = l + (r - l) / 2;
// 计算中间值
if (count(mid) <= k) {
// 如果以mid为每组和最大值时分组数小于等于k
ans = mid;
// mid可能是答案,更新答案
r = mid - 1;
// 缩小右边界
}
else {
l = mid + 1;
// 否则增大左边界
}
}
cout << ans << endl;
// 输出最终找到的答案
return 0;
}
知识点总结:
- 二分查找算法:掌握二分查找的基本思想和实现方式,能够在有序区间内快速查找满足条件的解。
- 函数封装:将统计分组数的功能封装成
count
函数,提高代码的可读性和可维护性。 - 区间处理:理解如何确定二分查找的区间范围,并根据条件调整区间边界以找到最优解。