一、选择题
第 1 题
题目:C++ 中,bool 类型的变量占用字节数为( )。
A. 1
B. 2
C. 3
D. 4
答案:A
解析:
C++ 标准规定,bool
类型至少占用 1 字节(1 byte),用于存储true
(非 0)或false
(0)。尽管逻辑上只需 1 位,但内存分配以字节为最小单位,因此选 A。
考点:C++ 基础数据类型的内存占用。
重点:掌握bool
、char
、int
、double
等类型的字节数(如char
占 1 字节,int
通常占 4 字节)。
教学方案:通过对比表格讲解数据类型,强调bool
的特殊性(1 字节而非 1 位),结合内存对齐原则理解。
第 2 题
题目:以下关于 C++ 结构体的说法,正确的是( )。
A. 结构体中只能包含成员变量,不能包含成员函数
B. 结构体不能从另一个结构体继承
C. 结构体里面可以包含静态成员变量
D. 结构体里面不能包含构造函数
答案:C
解析:
- A 错误:结构体可包含成员函数(与类的区别仅在于默认访问权限为
public
)。 - B 错误:结构体支持继承(语法与类相同,默认
public
继承)。 - C 正确:结构体允许静态成员变量(属于类型本身,而非实例)。
- D 错误:结构体可定义构造函数,用于初始化成员。
考点:结构体与类的特性对比。
重点:理解结构体的成员类型(变量、函数、静态成员、构造函数),区分结构体与类的默认访问权限。
教学方案:编写包含成员函数和构造函数的结构体示例,演示继承语法,对比类与结构体的异同。
第 3 题
题目:设只含根结点的二叉树高度为 1,共有 62 个结点的完全二叉树的高度为( )。
A. 4
B. 5
C. 6
D. 7
答案:C
解析:
完全二叉树高度h
满足:
- 前
h-1
层是满二叉树,结点数为2^(h-1)-1
; - 第
h
层至少 1 个结点,最多2^(h-1)
个结点。
计算: - 当
h=5
时,前 4 层结点数为2^4-1=15
,总结点数最多15+8=23
(<62,不满足); - 当
h=6
时,前 5 层结点数为2^5-1=31
,总结点数最多31+32=63
(≥62,满足)。
故高度为 6,选 C。
考点:完全二叉树的结点数与高度关系。
重点:掌握公式2^(h-1) ≤ 结点数 ≤ 2^h - 1
,通过不等式求解高度。
教学方案:画图演示满二叉树与完全二叉树的结构,推导高度计算公式,通过例题强化计算。
第 4 题
题目:以下关于数组的说法,不正确的是( )。
A. 数组中所有元素的类型必须都相同
B. 数组中各元素在内存中是顺序存放的
C. 数组最后一个元素的索引是数组的长度
D. 数组名的第一个字符可以是下划线
答案:C
解析:
- A 正确:数组元素类型必须统一(如
int arr[5]
所有元素均为int
)。 - B 正确:数组在内存中连续存储,元素地址递增。
- C 错误:索引从 0 开始,最后一个元素索引为
长度-1
(如长度 5 的数组索引 0~4)。 - D 正确:数组名是标识符,允许以下划线开头(如
_arr
)。
考点:数组的基本特性。
重点:强调索引越界风险,区分数组长度与最大索引(长度 - 1)。
教学方案:通过代码示例演示数组定义、访问,故意写出越界代码(如arr[5]
对长度 5 的数组),观察错误现象。
第 5 题
题目:执行以下代码,输出结果是( )。
cpp
#include <iostream>
using namespace std;
int f(int k) {
if (k == 1) return 3;
return 2 * f(k - 1) + 1;
}
int main() {
int n = 6;
cout << f(n);
return 0;
}
A. 127
B. 97
C. 63
D. 126
答案:A
解析:
递归函数递推关系:
- 基例:
f(1)=3
- 递推:
f(k)=2*f(k-1)+1
展开计算: f(2)=2×3+1=7
f(3)=2×7+1=15
f(4)=2×15+1=31
f(5)=2×31+1=63
f(6)=2×63+1=127
考点:递归函数的递推计算。
重点:理解递归终止条件与递推公式,可转化为等比数列(通项公式:f(k)=2^(k+1)-1
)。
教学方案:用递归展开法逐步计算,引入数学归纳法推导通项公式,避免深层递归导致栈溢出。
二、编程题
第 6 题:特殊运算符
题目描述:
定义运算符 “>>>N” 为提取 N 的前两位数字(如 257→25,182→18),计算 N - (>>>N)。
输入:三位数 N(100<N<1000)。
输出:N 减去前两位的结果。
样例输入:257 → 输出 232(257-25=232)。
答案代码:
cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int first_two = n / 10; // 提取前两位(如257/10=25)
cout << n - first_two << endl;
return 0;
}
解析:
三位数的前两位可通过整数除法n//10
得到(如 933//10=93),直接计算差值即可。
考点:数字处理(整数除法提取高位)。
重点:掌握//
和%
的用法,明确三位数的结构(百位 ×100 + 十位 ×10 + 个位)。
教学方案:通过分解数字的各位(百位、十位、个位)演示n//100
、n//10%10
、n%10
,强调整数除法的应用。
第 7 题:四叶玫瑰数
题目描述:
找出四位数中各位数字的四次方之和等于自身的数(如 1634=1⁴+6⁴+3⁴+4⁴),输出 N~M 范围内的数。
输入:N 和 M(1≤N≤M≤1e6)。
输出:按从小到大顺序的四叶玫瑰数。
样例输入:1234 2345 → 输出 1634。
答案思路:
- 仅枚举四位数(1000≤num≤9999),减少计算量;
- 分解各位数字:千位
a=num/1000
,百位b=num/100%10
,十位c=num/10%10
,个位d=num%10
; - 计算四次方和,若等于原数则输出。
代码框架:
cpp
#include <iostream>
using namespace std;
bool is_rose(int num) {
int a = num / 1000, b = num / 100 % 10, c = num / 10 % 10, d = num % 10;
return a*a*a*a + b*b*b*b + c*c*c*c + d*d*d*d == num;
}
int main() {
int n, m;
cin >> n >> m;
for (int i = max(n, 1000); i <= min(m, 9999); i++) {
if (is_rose(i)) cout << i << " ";
}
return 0;
}
考点:枚举算法与数字分解。
重点:限定枚举范围(仅四位数),优化循环条件,避免无效计算(如处理 N<1000 或 M>9999 的情况)。
教学方案:讲解 “四叶玫瑰数” 的数学定义,演示数字分解方法,强调提前过滤非四位数以提高效率。
第 8 题:质因数的个数
题目描述:
统计 N~M 之间每个数的质因数个数(重复质因数算多个,如 8=2×2×2,个数为 3),求最大值。
输入:N 和 M(1≤N≤M≤1e7)。
输出:最大质因数个数。
样例输入:6 10 → 输出 3(8 的质因数个数为 3)。
答案思路:
- 对每个数
num
进行质因数分解:从 2 到√num 试除,统计每个质因数的次数; - 若试除后
num>1
,说明剩余部分是质数,次数加 1; - 遍历 N~M,记录最大次数。
代码核心:
cpp
int count_prime_factors(int num) {
int count = 0;
for (int i = 2; i * i <= num; i++) {
while (num % i == 0) { // 统计i的次数
count++;
num /= i;
}
}
if (num > 1) count++; // 处理剩余质数(如7、13等)
return count;
}
考点:质因数分解与贪心统计。
重点:试除法分解质因数,区分质因数的 “种类” 与 “个数”(本题统计个数,包括重复)。
教学方案:通过示例(如 12=2²×3¹,个数 2+1=3)讲解质因数个数的定义,演示试除过程,强调从小到大试除以确保质因数。
第 9 题:最大的矩形纸片
题目描述:
在直方图中找最大矩形面积(高度数组 [3,2,1,4,5,2] 的最大面积为 8)。
输入:N(列数)和高度数组。
输出:最大矩形面积。
答案思路:
使用单调栈算法:
- 维护一个单调递增栈,存储索引,对应高度递增;
- 遍历每个高度,找到左右两边第一个比它小的位置,计算宽度
right - left - 1
,面积 = 高度 × 宽度; - 处理边界条件(数组末尾加 0,确保栈中元素全部弹出)。
代码框架:
cpp
#include <iostream>
#include <stack>
using namespace std;
long long max_area(int n, int* heights) {
stack<int> st;
long long res = 0;
for (int i = 0; i <= n; i++) { // 末尾加0,处理所有元素
while (!st.empty() && (i == n || heights[st.top()] >= heights[i])) {
int h = heights[st.top()]; st.pop();
int w = st.empty() ? i : i - st.top() - 1;
res = max(res, (long long)h * w);
}
st.push(i);
}
return res;
}
考点:直方图最大矩形面积(单调栈算法)。
重点:理解单调栈的作用(快速找到左右边界),处理数据类型溢出(使用long long
)。
教学方案:通过直方图画图演示单调栈的工作流程,解释每个步骤的意义,对比暴力法与单调栈的时间复杂度(O (n) vs O (n²))。
第 10 题:数字游戏
题目描述:
交替调整最小数到第二小、最大数到第二大,直到不同数少于 3 个,输出调整次数、最终最小和最大值。
输入:数组。
输出:次数、最终最小值、最大值。
样例输入:1 3 4 2 → 调整 2 次,结果 2 2 3。
答案思路:
- 每次操作后排序数组,统计不同数的数量;
- 第奇数次操作:将所有最小数改为第二小数;
- 第偶数次操作:将所有最大数改为第二大数;
- 直到不同数≤2 时终止。
代码核心:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n;
vector<int> nums;
cin >> n >> nums;
int count = 0;
bool is_min_turn = true; // 第一次调整最小数
while (true) {
sort(nums.begin(), nums.end());
// 统计不同数
int unique = 1;
for (int i = 1; i < n; i++) {
if (nums[i] != nums[i-1]) unique++;
}
if (unique < 3) break;
if (is_min_turn) {
int second_min = nums[1];
for (int i = 0; i < n; i++) {
if (nums[i] == nums[0]) nums[i] = second_min;
}
} else {
int second_max = nums[n-2];
for (int i = 0; i < n; i++) {
if (nums[i] == nums[n-1]) nums[i] = second_max;
}
}
count++;
is_min_turn = !is_min_turn;
}
sort(nums.begin(), nums.end());
cout << count << " " << nums[0] << " " << nums.back() << endl;
return 0;
}
考点:模拟算法与排序。
重点:每次操作后排序,正确识别第二小 / 第二大数,处理边界情况(如所有数相同)。
教学方案:通过示例演示调整过程,强调排序的重要性,讲解如何统计不同数的数量(遍历或使用集合)。
第 11 题:活动人数
题目描述:
树状结构中,选某部门则不能选直接下级,求最大人数(树形动态规划)。
输入:部门数 N,每个部门的上级 F、编号 S、人数 C。
输出:最大人数。
样例输入:6 个部门,输出 11(选部门 1、4、5、6,人数 2+3+2+4=11)。
答案思路:
每个节点有两种状态:
dp[u][1]
:选节点 u 时,最大人数(等于 u 的人数 + 所有子节点不选的最大值);dp[u][0]
:不选节点 u 时,最大人数(等于所有子节点选或不选的最大值之和)。
通过深度优先搜索(DFS)递归计算每个节点的状态。
代码框架:
cpp
#include <iostream>
#include <vector>
using namespace std;
struct Node {
int c;
vector<int> children;
};
Node nodes[100001];
int dp[100001][2]; // dp[u][1]选,dp[u][0]不选
void dfs(int u) {
dp[u][1] = nodes[u].c; // 选当前节点,初始化为自身人数
for (int v : nodes[u].children) {
dfs(v);
dp[u][1] += dp[v][0]; // 子节点不能选
dp[u][0] += max(dp[v][0], dp[v][1]); // 子节点可选或不选
}
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int f, s, c;
cin >> f >> s >> c;
nodes[s].c = c;
if (f != 0) nodes[f].children.push_back(s); // 构建树结构
}
// 找根节点(上级为0的节点)
int root = 0;
for (int i = 1; i <= n; i++) {
if (nodes[i].children.size() > 0 && (root == 0 || ...)) {
// 实际应遍历找到f=0的s
// 正确方法:记录每个节点的父节点,找父节点为0的节点
}
// 简化:假设根节点是1(需根据输入正确查找)
root = 1; // 实际需遍历所有节点,找到f=0对应的s
}
dfs(root);
cout << max(dp[root][0], dp[root][1]) << endl;
return 0;
}
考点:树形动态规划(树上的选与不选问题)。
重点:树的存储(邻接表),状态转移方程的推导,根节点的确定(上级为 0 的节点)。
教学方案:讲解树的基本概念,演示状态转移方程的推导过程,通过样例分析选与不选的决策对结果的影响,强调递归 DFS 的实现。
详细教学方案
一、选择题模块
-
数据类型与内存:
- 对比
bool
、char
、int
等类型的字节数,通过代码sizeof(bool)
验证。 - 讲解内存对齐原则,解释为何
bool
占 1 字节而非 1 位。
- 对比
-
结构体与类:
- 编写包含成员函数、构造函数、静态成员的结构体示例,演示继承语法(
struct B : public A
)。 - 对比结构体与类的默认访问权限(
public
vsprivate
)。
- 编写包含成员函数、构造函数、静态成员的结构体示例,演示继承语法(
-
二叉树性质:
- 画图演示满二叉树与完全二叉树,推导高度计算公式
h = floor(log2(n)) + 1
。 - 通过练习题(如结点数 30、63 的高度)强化计算。
- 画图演示满二叉树与完全二叉树,推导高度计算公式
-
数组基础:
- 演示数组定义、初始化、越界访问,用调试工具观察内存布局。
- 强调索引从 0 开始,通过错误案例(如访问
arr[len]
)加深印象。
-
递归函数:
- 用递归展开法计算第 5 题,引入数学归纳法推导通项公式
f(k)=2^(k+1)-1
。 - 讲解递归与迭代的转换,避免栈溢出(如限制递归深度)。
- 用递归展开法计算第 5 题,引入数学归纳法推导通项公式
二、编程题模块
-
数字处理(第 6 题):
- 讲解整数除法
//
和取余%
的用法,分解三位数的百位、十位、个位。 - 设计变式题:提取前两位(三位数)、前三位(四位数),计算差值。
- 讲解整数除法
-
枚举算法(第 7 题):
- 限定枚举范围(四位数),避免无效循环(如 N=500 时从 1000 开始枚举)。
- 优化数字分解:用数学公式快速获取各位数字,减少计算量。
-
质因数分解(第 8 题):
- 演示试除法分解质因数,强调从小到大试除确保质因数(如先除 2,再除 3,直到√num)。
- 区分质因数的 “个数” 与 “种类”(本题统计个数,包含重复)。
-
单调栈算法(第 9 题):
- 通过直方图动画演示单调栈的工作流程,解释每个元素的左右边界如何确定。
- 对比暴力法(O (n²))与单调栈(O (n))的效率,强调算法优化的重要性。
-
模拟与排序(第 10 题):
- 通过示例表格记录每次调整后的数组状态,演示排序的作用(快速找到最小 / 最大值)。
- 处理边界情况:如所有数相同(直接输出 0 次),或只有两种不同数(无需调整)。
-
树形动态规划(第 11 题):
- 讲解树的存储方式(邻接表),如何构建树结构(根据输入的上下级关系)。
- 推导状态转移方程:选当前节点则子节点不能选,不选则子节点可选或不选,取最大值。
- 通过样例分析递归过程,强调根节点的正确查找(上级为 0 的节点)。
三、实战训练
- 选择题:设计 10 道同类题目,涵盖数据类型、结构体、二叉树、数组、递归等考点,限时 5 分钟完成。
- 编程题:
- 第 6 题变式:处理四位数,提取前三位,计算差值。
- 第 8 题优化:预处理质数表,加速质因数分解(适用于大数据范围)。
- 第 11 题扩展:处理森林(多棵树),求所有树的最大人数之和。
- 调试技巧:
- 学会使用断点调试,观察递归过程或循环变量变化。
- 针对超时问题,分析算法时间复杂度,优化循环条件或选择更高效的算法(如单调栈替代双重循环)。
通过以上教学方案,学生可系统掌握 C++ 基础、算法思维和编程技巧,提升解决竞赛题目的能力。