init: 12 lessons + outline

This commit is contained in:
chengzi
2026-07-02 16:54:05 +08:00
commit 34bd44cf15
29 changed files with 10036 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# 忽略所有文件
/*
/*/
# 保留以下内容
!/GESP暑假班大纲.md
!/第01课-C++自带函数/
!/第02课-循环结构/
!/第03课-自定义函数/
!/第04课-数据类型与分支结构/
!/第05课-嵌套循环与图形打印/
!/第06课-字符数组与string/
!/第07课-数组计数法/
!/第08课-枚举与模拟/
!/第09课-GESP一级真题训练/
!/第10课-GESP二级真题训练一/
!/第11课-GESP二级真题训练二/
!/第12课-模拟考试/
# 保留各课内的 .md 文件及子文件
!*.md
!**/*.md
!.gitignore

204
GESP暑假班大纲.md Normal file
View File

@@ -0,0 +1,204 @@
# GESP暑假集训班大纲
## 课程信息
- **课程目标**巩固GESP一级内容备战9月等级考试融入二级核心知识
- **适合学生**学过一年C++,基础较好
- **课时安排**12节课 × 2小时
- **考试目标**9月GESP一级全员通过部分学生冲刺二级
---
## 课程大纲总览
| 节次 | 课程名称 | 等级 | 核心知识点 |
|:---:|----------|:---:|------------|
| 1 | C++自带函数 | 一级 | 标准库函数调用、头文件、字符判断函数 |
| 2 | 循环结构 + 数位拆解 | 一级 | for/while循环、数位拆解技巧、回文数判断 |
| 3 | 自定义函数 | 四级拓展 | 函数定义与调用、参数传递、返回值 |
| 4 | 数据类型与分支结构 | 一级 | 五种数据类型、ASCII码、if-else分支、打擂台 |
| 5 | 嵌套循环与图形打印 | 二级 | 嵌套循环执行流程、图形规律分析 |
| 6 | 字符数组与string | 二级 | string基本操作、字符串处理 |
| 7 | 数组计数法 | 二级 | 计数思想、频次统计、去重输出 |
| 8 | 枚举与模拟 | 三级工具 | 枚举思想、模拟算法 |
| 9 | GESP一级真题训练 | 冲刺 | 一级综合练习 |
| 10 | GESP二级真题训练一 | 冲刺 | 二级综合练习 |
| 11 | GESP二级真题训练二 | 冲刺 | 二级综合练习 |
| 12 | 模拟考试 | 冲刺 | 模拟考试环境、查漏补缺 |
---
## 各课详细知识点
### 第1课C++自带函数
**等级**:一级 | **核心**:正确选择并使用标准库函数
| 知识模块 | 具体内容 |
|----------|----------|
| 数学函数 | `sqrt`(开平方)、`abs`(绝对值)、`pow`(幂运算) |
| 最值与交换 | `max``min``swap` |
| 字符判断 | `isalpha``isdigit``islower``isupper` |
| 字符转换 | `tolower``toupper` |
| 头文件 | `<cmath>``<algorithm>``<cctype>` |
**考点**:函数名、头文件、参数类型、返回值
---
### 第2课循环结构 + 数位拆解
**等级**:一级 | **核心**:循环边界与数位拆解
| 知识模块 | 具体内容 |
|----------|----------|
| 三种循环 | `for`(次数明确)、`while`(条件控制)、`do-while`(至少执行一次) |
| 数位拆解 | 取个位 `n%10`、去个位 `n/10`、求各位和、反转数字 |
| 回文数判断 | 反转后与原数比较 |
| 水仙花数 | 三位数各位立方和等于自身 |
**核心口诀**:取个位、拼新数、去个位
---
### 第3课自定义函数拓展
**等级**:四级拓展 | **核心**:函数的参数、返回值与调用过程
| 知识模块 | 具体内容 |
|----------|----------|
| 函数定义 | 返回类型、函数名、参数列表、函数体 |
| 参数传递 | 形参vs实参、值传递 |
| 返回值 | `return`语句、`void`函数 |
| 函数重载 | 同名不同参数 |
| 变量作用域 | 局部变量vs全局变量 |
**典型题目**:判断素数函数
---
### 第4课数据类型与分支结构
**等级**:一级 | **核心**:数据类型、类型转换与数据范围
| 知识模块 | 具体内容 |
|----------|----------|
| 五种数据类型 | `int``char``double``bool``string` |
| ASCII码 | 字符本质是整数,`'0'`=48、`'A'`=65、`'a'`=97 |
| 类型转换 | 隐式转换、强制转换、`1.0*sum/n`触发浮点运算 |
| 分支结构 | `if``else if``else``switch`、三目运算 |
| 打擂台 | 普通打擂台、带并列规则的二次打擂台 |
**核心口诀**int存整数、char单字符、double小数、bool对错、string字符串
---
### 第5课嵌套循环与图形打印
**等级**:二级 | **核心**:嵌套循环中的行列关系
| 知识模块 | 具体内容 |
|----------|----------|
| 嵌套循环 | 外层管行、内层管列 |
| 直角三角形 | 左下、右下、左上、右上 |
| 等腰三角形 | 第i行空格`n-i`个,星号`2i-1`个 |
| 菱形 | 正等腰 + 倒等腰 |
**核心公式**
- 等腰三角形星号数:`2i - 1`
- 前导空格数:`n - i`
---
### 第6课字符数组与string
**等级**:二级 | **核心**:字符串输入和逐字符处理
| 知识模块 | 具体内容 |
|----------|----------|
| 输入方式 | `cin >> s`(遇空格停)、`getline(cin, s)`(读整行) |
| 基本操作 | `s.length()`取长度、`s[i]`访问字符 |
| 查找替换 | `s.find()``s.replace()``s.substr()` |
| 字符统计 | 配合`isupper``islower``isdigit`分类统计 |
| 回文判断 | 首尾字符两两比较 |
---
### 第7课数组计数法
**等级**:二级 | **核心**:数值映射为下标的计数思想
| 知识模块 | 具体内容 |
|----------|----------|
| 核心写法 | `cnt[x]++` |
| 频次统计 | 统计每个数字出现次数 |
| 最高频查找 | 打擂台找众数 |
| 去重升序输出 | 遍历计数数组,天然有序 |
**适用条件**:数值范围有限,可作为数组下标
---
### 第8课枚举与模拟
**等级**:三级工具降维 | **核心**:枚举范围与模拟执行顺序
| 知识模块 | 具体内容 |
|----------|----------|
| 枚举思想 | 列举所有可能,逐一验证 |
| 枚举优化 | 缩小范围、提前终止 |
| 模拟思想 | 按题目描述一步步翻译成代码 |
| 典型题目 | 鸡兔同笼、找完数、斐波那契数列 |
**枚举vs模拟**:枚举在"答案空间"中找,模拟在"时间步骤"中走
---
### 第9-11课真题训练
**目标**:综合运用所学知识,提高解题速度
| 训练内容 | 涵盖知识点 |
|----------|------------|
| 一级真题 | 输入输出、数据类型、分支、循环、数位处理 |
| 二级真题 | 数组、字符串、嵌套循环、计数法 |
---
### 第12课模拟考试
**目标**:模拟考试环境,查漏补缺
| 内容 | 说明 |
|------|------|
| 考试环境 | 120分钟100分60分合格 |
| 时间管理 | 先易后难留10分钟检查 |
| 检查清单 | 头文件、变量初始化、数组越界、输出格式 |
---
## 核心考点速查表
| 考点类型 | 对应课程 | 关键技巧 |
|----------|----------|----------|
| 输入输出 | 1、4 | `cin/cout``getline` |
| 数据类型 | 4 | int/char/double的选择、ASCII码转换 |
| 分支结构 | 4 | if-else链、switch、打擂台 |
| 循环结构 | 2、5 | for/while选择、嵌套循环、图形规律 |
| 数组操作 | 7、9 | 遍历、计数、排序、去重 |
| 字符串处理 | 6 | string操作、字符判断、回文 |
| 函数 | 3 | 定义调用、参数返回值 |
| 算法思想 | 8 | 枚举、模拟 |
---
## 打油诗口诀汇总
1. **自带函数**:自带函数真方便,头文件里全找见
2. **自定义函数**:函数写好存身旁,起名调用响当当
3. **数据类型**int存整数范围广char单字符本质藏
4. **打擂台**:擂台之上比高矮,先请一位站上台
5. **数位拆解**:循环取位倒着排,个位拼到新数来
6. **回文数**:反转数字比原数,相等就是回文数
7. **图形打印**:外层循环管行数,内层循环管列数
8. **数组计数**:数值下标来对号,每次出现次数高
9. **枚举模拟**:枚举就是全试过,模拟照着步骤走
---
## 参考资源
- **GESP官方网站**https://gesp.ccf.org.cn
- **刷题平台**https://www.luogu.com.cn洛谷
- **考试时长**120分钟
- **满分**100分
- **合格线**60分

View File

@@ -0,0 +1,181 @@
# 第1课C++自带函数
## 教学目标
- 学会查阅和使用C++标准库自带函数
- 掌握 `<cmath>``<algorithm>``<cctype>` 三个常用头文件
- 能用自带函数解决计算问题,体会"站在巨人的肩膀上"
## 核心考点:正确选择并使用标准库函数
### 考什么
根据题目任务选择正确的________同时写对________、参数和返回值。
### 必须理解
调用函数要过三关它属于哪个________传入什么________返回什么________。
### 核心写法
```cpp
#include <________> // sqrt、pow
#include <________> // max、min、swap
double ans = ________(x);
int ma = ________(a, b);
```
### 常见错误
- `pow` 的返回结果能否一律当作精确整数________________
### 判断是否掌握
- [ ] 我能根据任务说出函数名和头文件。
- [ ] 我能解释函数参数与返回值的含义。
- [ ] 我能独立写出正确的调用语句。
---
## 函数速查表
### 一、数学计算函数(头文件:`______`
| 函数 | 参数 | 返回值 | 示例 | 说明 |
|------|------|--------|------|------|
| `sqrt(x)` | x — __________ | __________ — x 的平方根 | `sqrt(16)``___` | 开根号 |
| `abs(x)` | x — __________ | __________ — x 的绝对值 | `abs(-5)``___` | 去掉负号 |
| `pow(a, b)` | a — ______b — ______ | __________ — a 的 b 次方 | `pow(2, 3)``___` | aᵇ |
### 二、最值与交换函数(头文件:`______`
| 函数 | 参数 | 返回值 | 示例 | 说明 |
|------|------|--------|------|------|
| `max(a, b)` | 两个 __________ 的值 | __________ — 较大值 | `max(3, 7)``___` | 两数取大 |
| `min(a, b)` | 两个 __________ 的值 | __________ — 较小值 | `min(3, 7)``___` | 两数取小 |
| `swap(a, b)` | 两个 __________ 的**变量** | ________(无返回值) | `swap(x, y)`__________ | 交换两变量的值 |
### 三、字符判断函数(头文件:`______`
| 函数 | 参数 | 返回值 | 示例 | 说明 |
|------|------|--------|------|------|
| `isalpha(c)` | c — __________ | ____ — 是字母返回 `true` | `isalpha('A')``____` | 判断是否为字母 |
| `isdigit(c)` | c — __________ | ____ — 是数字返回 `true` | `isdigit('5')``____` | 判断是否为数字 |
| `islower(c)` | c — __________ | ____ — 是小写字母返回 `true` | `islower('a')``____` | 判断是否为小写 |
| `isupper(c)` | c — __________ | ____ — 是大写字母返回 `true` | `isupper('A')``____` | 判断是否为大写 |
### 四、字符转换函数(头文件:`______`
| 函数 | 参数 | 返回值 | 示例 | 说明 |
|------|------|--------|------|------|
| `tolower(c)` | c — __________ | ____ — 对应的小写字母 ASCII 码 | `tolower('A')``___` | 大写转小写 |
| `toupper(c)` | c — __________ | ____ — 对应的大写字母 ASCII 码 | `toupper('a')``___` | 小写转大写 |
---
## 课堂编程题
### 课堂1三点中最近的一对【适中】
**题目描述:** 输入平面上三个点的坐标,求三对点之间的最短距离,结果保留两位小数。要求使用 `sqrt``pow``min`
**输入格式:** 输入六个实数,依次表示三个点的横坐标和纵坐标。
**输出格式:** 输出最短距离,保留两位小数。
**数据范围:** 每个坐标的绝对值不超过 1000。
**输入样例:**
```text
0 0 3 4 0 2
```
**输出样例:**
```text
2.00
```
**解题思路:** 用勾股定理分别求出三段距离,再用 `min` 取最小值。
**易错提醒:** `sqrt` 返回浮点数;格式化输出需要包含 `<iomanip>`
---
### 课堂2整理三个数【适中】
**题目描述:** 输入三个整数,第一行输出最大值和最小值;再使用 `swap` 将三个变量调整为升序,第二行输出调整结果。
**输入格式:** 输入三个整数。
**输出格式:** 第一行输出最大值和最小值;第二行输出升序结果。
**数据范围:** `-10^9≤a,b,c≤10^9`
**输入样例:**
```text
8 2 5
```
**输出样例:**
```text
8 2
2 5 8
```
**解题思路:** 用嵌套的 `max``min` 求最值;再进行三次相邻比较,需要时调用 `swap`
**易错提醒:** `swap` 的参数必须是两个变量,不能传入 `a+b` 这样的表达式。
---
### 课堂3去掉最高分和最低分【适中】
**题目描述:** 输入若干个评委分数,去掉一个最高分和一个最低分,输出剩余分数之和。不得使用排序。
**输入格式:** 第一行输入评委人数;第二行输入所有评委给出的分数。
**输出格式:** 输出剩余分数之和。
**数据范围:** `3≤n≤100`,分数为 `0100`
**输入样例:**
```text
5
9 7 10 8 6
```
**输出样例:**
```text
24
```
**解题思路:** 遍历时同时计算总分、最高分和最低分,最后用总分减去两个极值。
**易错提醒:** 最高分和最低分要用第一个合法分数初始化;即使最高分重复,也只去掉一个。
---
### 课堂4字符身份证【适中】
**题目描述:** 输入一个可见字符。若为字母,输出 `LETTER` 和相反大小写的字符;若为数字,输出 `DIGIT`;否则输出 `OTHER`
**输入格式:** 一个可见字符。
**输出格式:** 按题目要求输出。
**数据范围:** 标准 ASCII 可见字符。
**输入样例:**
```text
G
```
**输出样例:**
```text
LETTER g
```
**解题思路:**`isalpha``isdigit` 分类,用 `islower``isupper` 判断大小写,再调用 `toupper``tolower` 转换。
**易错提醒:** `tolower``toupper` 返回的是字符编码,输出字符时应转换为 `char`
---
## 课后作业
> 请完成本课对应的课后作业,见编程题文件。

View File

@@ -0,0 +1,187 @@
# 第1课C++自带函数
## 教学目标
- 学会查阅和使用C++标准库自带函数
- 掌握 `<cmath>``<algorithm>``<cctype>` 三个常用头文件
- 能用自带函数解决计算问题,体会"站在巨人的肩膀上"
## 核心考点:正确选择并使用标准库函数
### 考什么
题目通常给出一个计算或处理任务,要求选择合适的标准库函数,并写对头文件、参数和返回值。例如开平方用 `sqrt`,求最大值用 `max`,判断字符用 `isdigit`。考试不只考“记住函数名”,还考能否把函数正确放进完整程序。
### 必须理解
调用标准库函数要过三关:第一,函数属于哪个头文件;第二,传入参数的类型和含义;第三,返回结果是什么类型。例如 `sqrt(x)` 返回浮点数,即使 x 是整数,也要注意精度和类型。
### 核心写法
```cpp
#include <cmath> // sqrt、pow
#include <algorithm> // max、min、swap
#include <cctype> // isdigit、isalpha
double root = sqrt(x);
int bigger = max(a, b);
```
先根据任务选函数,再补头文件,最后检查参数范围和返回类型。
### 常见错误
- 忘记包含对应头文件,导致函数未声明。
- 认为 `pow` 一定返回整数,直接用于要求精确整数的运算,可能受到浮点误差影响。
- 只背函数名,不检查参数是否满足要求,例如对负数直接开平方。
### 判断是否掌握
看到“开平方、绝对值、最值、交换、字符分类”时,能说出函数名、头文件、关键参数和返回结果,并能独立写出一段正确调用代码。
---
## 函数速查表
### 一、数学计算函数(头文件:`<cmath>`
| 函数 | 参数 | 返回值 | 示例 | 说明 |
|------|------|--------|------|------|
| `sqrt(x)` | `double x` — 一个非负数 | `double` — x 的平方根 | `sqrt(16)``4.0` | 开根号 |
| `abs(x)` | `int x` — 一个整数 | `int` — x 的绝对值 | `abs(-5)``5` | 去掉负号 |
| `pow(a, b)` | `double a` — 底数,`double b` — 指数 | `double` — a 的 b 次方 | `pow(2, 3)``8.0` | aᵇ |
### 二、最值与交换函数(头文件:`<algorithm>`
| 函数 | 参数 | 返回值 | 示例 | 说明 |
|------|------|--------|------|------|
| `max(a, b)` | 两个同类型的值 | 同类型 — 较大值 | `max(3, 7)``7` | 两数取大 |
| `min(a, b)` | 两个同类型的值 | 同类型 — 较小值 | `min(3, 7)``3` | 两数取小 |
| `swap(a, b)` | 两个同类型的**变量** | `void`(无返回值) | `swap(x, y)` → x和y交换 | 交换两变量的值 |
### 三、字符判断函数(头文件:`<cctype>`
| 函数 | 参数 | 返回值 | 示例 | 说明 |
|------|------|--------|------|------|
| `isalpha(c)` | `char c` — 一个字符 | `bool` — 是字母返回 `true` | `isalpha('A')``true` | 判断是否为字母A~Z 或 a~z |
| `isdigit(c)` | `char c` — 一个字符 | `bool` — 是数字返回 `true` | `isdigit('5')``true` | 判断是否为数字0~9 |
| `islower(c)` | `char c` — 一个字符 | `bool` — 是小写字母返回 `true` | `islower('a')``true` | 判断是否为小写字母a~z |
| `isupper(c)` | `char c` — 一个字符 | `bool` — 是大写字母返回 `true` | `isupper('A')``true` | 判断是否为大写字母A~Z |
### 四、字符转换函数(头文件:`<cctype>`
| 函数 | 参数 | 返回值 | 示例 | 说明 |
|------|------|--------|------|------|
| `tolower(c)` | `char c` — 一个字符 | `int` — 对应的小写字母 ASCII 码 | `tolower('A')``97`(即 `'a'` | 大写转小写 |
| `toupper(c)` | `char c` — 一个字符 | `int` — 对应的大写字母 ASCII 码 | `toupper('a')``65`(即 `'A'` | 小写转大写 |
> **注意**`tolower` 和 `toupper` 返回的是 `int`ASCII 码),输出时转回 `char``cout << (char)tolower('A');` 输出 `a`。
---
## 题目一:计算两点之间的距离
### 核心代码(一句话)
> 两点距离的核心:**勾股定理**`sqrt` 和 `pow` 配合使用,一行搞定。
### 自然语言思路
小明站在平面上的 A 点 `(x₁, y₁)`,小红站在 B 点 `(x₂, y₂)`,求两人之间的直线距离。
第一步,算出**水平方向**差了多少:`x₁ - x₂`
第二步,算出**垂直方向**差了多少:`y₁ - y₂`
第三步,把两个差值**分别平方**,平方就用 `pow(差值, 2)`
第四步,两个平方**加起来**。
第五步,对和**开根号**,用 `sqrt`,得到最终距离。
第六步,题目一般要求保留小数,用 `fixed << setprecision(2)` 保留两位。
> 其实就是把"勾股定理"翻译成代码:`c = sqrt(a² + b²)`。
### 伪代码
```
1. 输入 x1, y1, x2, y2 (四个浮点数)
2. 计算 dx = x1 - x2 (水平距离)
3. 计算 dy = y1 - y2 (垂直距离)
4. 计算 dist = sqrt( pow(dx, 2) + pow(dy, 2) )
5. 输出 dist保留两位小数
```
### 真实代码
```cpp
#include <iostream>
#include <cmath> // sqrt、pow 在这里
#include <iomanip> // fixed、setprecision 在这里
using namespace std;
int main() {
double x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
double dist = sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
cout << fixed << setprecision(2) << dist << endl;
return 0;
}
```
### 注意点
1. **头文件别忘**`sqrt``pow``<cmath>` 里,格式化输出在 `<iomanip>` 里。
2. **pow 的第二个参数**`pow(底数, 指数)`,指数写 `2` 表示平方,写 `0.5` 也能表示开方(和 sqrt 等价)。
3. **坐标相减顺序无所谓**`pow(x1-x2, 2)``pow(x2-x1, 2)` 结果一样,因为平方消掉了负号。
4. **保留小数的另一种写法**`printf("%.2f", dist)` 也可以,需要 `<cstdio>`
---
## 题目二:(待橙子补充)
> 建议覆盖:`max/min`、`swap` 等 `<algorithm>` 函数
---
### 核心代码(一句话)
>
### 自然语言思路
### 伪代码
### 真实代码
```cpp
```
### 注意点
---
## 题目三:(待橙子补充)
> 建议覆盖:`isalpha`、`isdigit`、`islower`、`isupper`、`tolower`、`toupper` 等 `<cctype>` 函数
---
### 核心代码(一句话)
>
### 自然语言思路
### 伪代码
### 真实代码
```cpp
```
### 注意点
---
## 本课打油诗
> 自带函数真方便,头文件里全找见。
> 省心省力少出错,一行代码就实现。
---
## 课后作业
(待补充)
1.
2.

View File

@@ -0,0 +1,632 @@
# 第1课编程题教师版
## 本课知识点覆盖表
| 核心知识点 | GESP等级 | 对应课堂题 |
|---|---|---|
| `sqrt``pow``abs``<cmath>` | 一级巩固/二级拓展 | 课堂1 |
| `max``min``swap``<algorithm>` | 二级 | 课堂2 |
| `max``min` 综合应用 | 二级 | 课堂3 |
| `isalpha``isdigit``islower``isupper``tolower``toupper` | 一级/二级 | 课堂4 |
> 难度均为本课内部的相对难度。参考程序采用 C++17。
## 一、课堂练习4题
### 课堂1三点中最近的一对【适中】
**题目描述:** 输入平面上三个点的坐标,求三对点之间的最短距离,结果保留两位小数。要求使用 `sqrt``pow``min`
**输入格式:** 输入六个实数,依次表示三个点的横坐标和纵坐标。
**输出格式:** 输出最短距离,保留两位小数。
**数据范围:** 每个坐标的绝对值不超过 1000。
**输入样例:**
```text
0 0 3 4 0 2
```
**输出样例:**
```text
2.00
```
**解题思路:** 用勾股定理分别求出三段距离,再用 `min` 取最小值。
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
#include <algorithm>
#include <iomanip>
using namespace std;
double distance(double x1, double y1, double x2, double y2) {
return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
}
int main() {
double x1, y1, x2, y2, x3, y3;
cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3;
double d1 = distance(x1, y1, x2, y2);
double d2 = distance(x1, y1, x3, y3);
double d3 = distance(x2, y2, x3, y3);
cout << fixed << setprecision(2) << min(d1, min(d2, d3));
return 0;
}
```
**易错提醒:** `sqrt` 返回浮点数;格式化输出需要包含 `<iomanip>`
### 课堂2整理三个数【适中】
**题目描述:** 输入三个整数,第一行输出最大值和最小值;再使用 `swap` 将三个变量调整为升序,第二行输出调整结果。
**输入格式:** 输入三个整数。
**输出格式:** 第一行输出最大值和最小值;第二行输出升序结果。
**数据范围:** `-10^9≤a,b,c≤10^9`
**输入样例:**
```text
8 2 5
```
**输出样例:**
```text
8 2
2 5 8
```
**解题思路:** 用嵌套的 `max``min` 求最值;再进行三次相邻比较,需要时调用 `swap`
**参考代码:**
```cpp
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int a, b, c;
cin >> a >> b >> c;
cout << max(a, max(b, c)) << " " << min(a, min(b, c)) << "\n";
if (a > b) swap(a, b);
if (b > c) swap(b, c);
if (a > b) swap(a, b);
cout << a << " " << b << " " << c;
return 0;
}
```
**易错提醒:** `swap` 的参数必须是两个变量,不能传入 `a+b` 这样的表达式。
### 课堂3去掉最高分和最低分【适中】
**题目描述:** 输入若干个评委分数,去掉一个最高分和一个最低分,输出剩余分数之和。不得使用排序。
**输入格式:** 第一行输入评委人数;第二行输入所有评委给出的分数。
**输出格式:** 输出剩余分数之和。
**数据范围:** `3≤n≤100`,分数为 `0100`
**输入样例:**
```text
5
9 7 10 8 6
```
**输出样例:**
```text
24
```
**解题思路:** 遍历时同时计算总分、最高分和最低分,最后用总分减去两个极值。
**参考代码:**
```cpp
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n, score, sum = 0, biggest, smallest;
cin >> n;
cin >> score;
sum = biggest = smallest = score;
for (int i = 1; i < n; i++) {
cin >> score;
sum += score;
biggest = max(biggest, score);
smallest = min(smallest, score);
}
cout << sum - biggest - smallest;
return 0;
}
```
**易错提醒:** 最高分和最低分要用第一个合法分数初始化;即使最高分重复,也只去掉一个。
### 课堂4字符身份证【适中】
**题目描述:** 输入一个可见字符。若为字母,输出 `LETTER` 和相反大小写的字符;若为数字,输出 `DIGIT`;否则输出 `OTHER`
**输入格式:** 一个可见字符。
**输出格式:** 按题目要求输出。
**数据范围:** 标准 ASCII 可见字符。
**输入样例:**
```text
G
```
**输出样例:**
```text
LETTER g
```
**解题思路:**`isalpha``isdigit` 分类,用 `islower``isupper` 判断大小写,再调用 `toupper``tolower` 转换。
**参考代码:**
```cpp
#include <iostream>
#include <cctype>
using namespace std;
int main() {
char c;
cin >> c;
if (isalpha(c)) {
cout << "LETTER ";
if (islower(c)) cout << (char)toupper(c);
else if (isupper(c)) cout << (char)tolower(c);
} else if (isdigit(c)) {
cout << "DIGIT";
} else {
cout << "OTHER";
}
return 0;
}
```
**易错提醒:** `tolower``toupper` 返回的是字符编码,输出字符时应转换为 `char`
## 二、课后作业3题
### 课后1离原点的距离【简单】
**题目描述:** 输入点 `(x,y)`,输出它到原点的距离,保留两位小数。
**输入格式:** 输入两个实数,依次表示横坐标和纵坐标。
**输出格式:** 距离,保留两位小数。
**数据范围:** 坐标绝对值不超过 1000。
**输入样例:**
```text
3 4
```
**输出样例:**
```text
5.00
```
**解题思路:** 根据勾股定理计算 `sqrt(pow(x,2)+pow(y,2))`
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
int main() {
double x, y;
cin >> x >> y;
cout << fixed << setprecision(2)
<< sqrt(pow(x, 2) + pow(y, 2));
return 0;
}
```
**易错提醒:** 不要忘记 `<cmath>``<iomanip>`
### 课后2两数差的绝对值【简单】
**题目描述:** 输入两个整数,输出它们差的绝对值。
**输入格式:** 输入两个整数。
**输出格式:** 输出 `|a-b|`
**数据范围:** 两数绝对值不超过 10000。
**输入样例:**
```text
3 9
```
**输出样例:**
```text
6
```
**解题思路:** 直接调用 `abs(a-b)`
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int a, b;
cin >> a >> b;
cout << abs(a - b);
return 0;
}
```
**易错提醒:** 先求差再取绝对值,不要误写成 `abs(a)-abs(b)`
### 课后3数字还是字母【简单】
**题目描述:** 输入一个字符。是数字输出 `1`,是字母输出 `2`,其他字符输出 `0`
**输入格式:** 一个字符。
**输出格式:** 一个整数。
**数据范围:** 标准 ASCII 可见字符。
**输入样例:**
```text
8
```
**输出样例:**
```text
1
```
**解题思路:** 依次调用 `isdigit``isalpha`
**参考代码:**
```cpp
#include <iostream>
#include <cctype>
using namespace std;
int main() {
char c;
cin >> c;
if (isdigit(c)) cout << 1;
else if (isalpha(c)) cout << 2;
else cout << 0;
return 0;
}
```
**易错提醒:** 字符数字 `'8'` 与整数 `8` 不同。
## 三、扩展练习7题
### 扩展1正方形对角线【基础综合】
**题目描述:** 输入正方形边长,输出对角线长度,保留三位小数。
**输入格式:** 输入一个正实数,表示正方形的边长。
**输出格式:** 对角线长度。
**数据范围:** `0<a≤10000`
**输入样例:**
```text
2
```
**输出样例:**
```text
2.828
```
**解题思路:** 对角线为 `sqrt(2)*a`
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
int main() {
double a;
cin >> a;
cout << fixed << setprecision(3) << sqrt(2.0) * a;
return 0;
}
```
**易错提醒:** 使用 `2.0` 可以明确进行浮点运算。
### 扩展2三个数的中间数【基础综合】
**题目描述:** 输入三个互不相同的整数,使用 `max``min` 输出大小居中的数。
**输入格式:** 三个整数。
**输出格式:** 中间数。
**数据范围:** 每个数的绝对值不超过 `10^9`
**输入样例:**
```text
9 2 6
```
**输出样例:**
```text
6
```
**解题思路:** 三数之和减去最大值和最小值。
**参考代码:**
```cpp
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
long long a, b, c;
cin >> a >> b >> c;
long long biggest = max(a, max(b, c));
long long smallest = min(a, min(b, c));
cout << a + b + c - biggest - smallest;
return 0;
}
```
**易错提醒:** 三数之和可能超过 `int`,这里使用 `long long`
### 扩展3成绩极差【中等】
**题目描述:** 输入若干个成绩,输出最低分、最高分以及两者之差。
**输入格式:** 第一行输入成绩数量;第二行输入所有成绩。
**输出格式:** 依次输出最低分、最高分和极差。
**数据范围:** `1≤n≤100`,成绩为 `0100`
**输入样例:**
```text
4
80 60 90 70
```
**输出样例:**
```text
60 90 30
```
**解题思路:** 用第一个成绩初始化最值,随后逐一调用 `max``min` 更新。
**参考代码:**
```cpp
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n, score, biggest, smallest;
cin >> n;
cin >> score;
biggest = smallest = score;
for (int i = 1; i < n; i++) {
cin >> score;
biggest = max(biggest, score);
smallest = min(smallest, score);
}
cout << smallest << " " << biggest << " " << biggest - smallest;
return 0;
}
```
**易错提醒:** 当只有一个成绩时,最低分和最高分相同,极差为 0。
### 扩展4口令规范化【中等】
**题目描述:** 输入一个不含空格的口令,把其中所有字母转成大写,数字和符号保持不变。
**输入格式:** 一个不含空格的字符串。
**输出格式:** 转换后的字符串。
**数据范围:** 字符串长度不超过 100。
**输入样例:**
```text
GesP2026!
```
**输出样例:**
```text
GESP2026!
```
**解题思路:** 逐个检查字符;是字母就调用 `toupper`
**参考代码:**
```cpp
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main() {
string s;
cin >> s;
for (int i = 0; i < (int)s.size(); i++) {
if (isalpha(s[i])) s[i] = (char)toupper(s[i]);
}
cout << s;
return 0;
}
```
**易错提醒:** 不要改动数字和符号。
### 扩展5离目标最近的数【中等】
**题目描述:** 输入 `n` 个整数和目标值 `t`,输出与 `t` 的差的绝对值最小的数;若并列,输出较小者。
**输入格式:** 第一行输入整数数量和目标值;第二行输入所有整数。
**输出格式:** 满足条件的整数。
**数据范围:** `1≤n≤100`,输入整数绝对值不超过 10000。
**输入样例:**
```text
4 10
8 12 15 3
```
**输出样例:**
```text
8
```
**解题思路:** 用第一个数初始化答案;比较 `abs(x-t)`,相等时比较数值大小。
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n, t, x, answer;
cin >> n >> t >> answer;
for (int i = 1; i < n; i++) {
cin >> x;
if (abs(x - t) < abs(answer - t) ||
(abs(x - t) == abs(answer - t) && x < answer)) {
answer = x;
}
}
cout << answer;
return 0;
}
```
**易错提醒:** 不能把答案初始化为 0因为 0 不一定在输入中。
### 扩展6最接近平均值的成绩【略难】
**题目描述:** 输入 `n` 个整数成绩,求平均值,并输出最接近平均值的成绩;若并列,输出较小者。
**输入格式:** 第一行输入成绩数量;第二行输入所有成绩。
**输出格式:** 一个成绩。
**数据范围:** `1≤n≤100`,成绩为 `0100`
**输入样例:**
```text
4
60 70 80 100
```
**输出样例:**
```text
80
```
**解题思路:** 先求浮点平均值,再使用 `abs` 进行二次打擂台。
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n, a[105], sum = 0;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
sum += a[i];
}
double average = 1.0 * sum / n;
int answer = a[0];
for (int i = 1; i < n; i++) {
double now = abs(a[i] - average);
double best = abs(answer - average);
if (now < best || (now == best && a[i] < answer)) {
answer = a[i];
}
}
cout << answer;
return 0;
}
```
**易错提醒:** 平均值必须使用浮点除法;并列规则不能遗漏。
### 扩展7统一大小写并分类统计【略难】
**题目描述:** 输入一个不含空格的字符串,把所有字母转成小写,并统计原字符串中大写字母、小写字母和数字的数量。
**输入格式:** 输入一个不含空格的字符串。
**输出格式:** 第一行输出转换后的字符串;第二行依次输出三类字符数量。
**数据范围:** 字符串长度为 1100。
**输入样例:**
```text
AbC2026xY
```
**输出样例:**
```text
abc2026xy
3 3 4
```
**解题思路:** 遍历原字符串,先分类计数,再对字母调用 `tolower`
**参考代码:**
```cpp
#include <iostream>
#include <cctype>
#include <string>
using namespace std;
int main() {
string s;
int upper = 0, lower = 0, digit = 0;
cin >> s;
for (char &c : s) {
if (isupper(c)) upper++;
else if (islower(c)) lower++;
else if (isdigit(c)) digit++;
if (isalpha(c)) c = (char)tolower(c);
}
cout << s << "\n";
cout << upper << " " << lower << " " << digit;
return 0;
}
```
**易错提醒:** 分类统计必须依据转换前的字符;数字不参与大小写转换。

View File

@@ -0,0 +1,160 @@
# 第2课循环结构 + 数位拆解
## 教学目标
- 掌握 for、while 和 do-while 循环的使用场景
- 掌握数位拆解技巧(取个位、去个位)
- 能用循环判断回文数
- 了解水仙花数的判断方法
## 核心考点:循环边界与数位拆解
### 考什么
判断循环次数和结束条件并用循环完成求各位和、________数字、判断回文数等任务。
### 必须理解
次数明确时常用________结束条件更自然时常用________。数位拆解每轮依次完成________、使用这一位、________。如果后面还要使用原数必须先________。
### 核心写法
```cpp
int num = ________;
int rev = 0;
while (num > 0) {
int d = num ________ 10;
rev = rev * ________ + d;
num ________ 10;
}
```
### `do-while` 循环
`do-while` 的特点:循环体至少执行 ______ 次。
```cpp
do {
// 循环体
} while (); // 结尾不要漏掉 ______
```
### 常见错误
- 忘记更新循环变量会发生什么________________
- `i < n``i <= n` 的执行次数有什么区别____________
- 为什么反转前要备份原数________________
### 判断是否掌握
- [ ] 我能判断循环的起点、终点和次数。
- [ ] 我能写出取个位与去个位。
- [ ] 我能独立完成数字反转。
---
## 课堂编程题
### 课堂1奇偶分组统计【适中】
**题目描述:** 输入正整数 `n`,分别求 `1n` 中奇数之和、偶数之和,并计算 `1n` 的乘积。若乘积超过 `10^18`,输出 `TOO LARGE`
**输入格式:** 输入一个正整数。
**输出格式:** 第一行奇数和与偶数和;第二行乘积或 `TOO LARGE`
**数据范围:** `1≤n≤30`
**输入样例:**
```text
5
```
**输出样例:**
```text
9 6
120
```
**解题思路:** 一个 `for` 循环同时完成奇偶累加;累乘前判断 `product>10^18/i`,避免溢出。
**易错提醒:** 范围是 `1n`,循环条件必须为 `i<=n`;累加器和累乘器的初值分别为 0 和 1。
---
### 课堂2数字分析报告【适中】
**题目描述:** 输入一个非负整数,输出它的位数、各位数字之和、最大数字和反转数。
**输入格式:** 输入一个非负整数。
**输出格式:** 依次输出位数、各位和、最大数字、反转数。
**数据范围:** `0≤n≤10^9`
**输入样例:**
```text
50230
```
**输出样例:**
```text
5 10 5 3205
```
**解题思路:** 备份原数,用 `while` 重复"取个位、使用个位、去个位";单独处理 `n=0`
**易错提醒:** 反转后的前导零不会保留0 本身有一位,不能被普通循环漏掉。
---
### 课堂3寻找第一个合格数【适中】
**题目描述:**`1n` 中寻找第一个同时满足"能被 7 整除、不能被 5 整除、各位和大于 10"的数。跳过所有含数字 4 的数;找到后立即输出,否则输出 `NONE`
**输入格式:** 输入一个正整数。
**输出格式:** 第一个合格数或 `NONE`
**数据范围:** `1≤n≤100000`
**输入样例:**
```text
100
```
**输出样例:**
```text
77
```
**解题思路:** 外层枚举数字;拆位检查含 4 和各位和。含 4 时 `continue`,找到答案时 `break`
**易错提醒:** `continue` 跳过本轮,`break` 结束整个当前循环,两者含义不同。
---
### 课堂4区间素数统计【适中·双层循环】
**题目描述:** 输入 `L``R`,输出闭区间内的所有素数及素数个数。
**输入格式:** 输入区间的左端点和右端点。
**输出格式:** 第一行按升序输出素数;第二行输出个数。没有素数时第一行输出 `NONE`
**数据范围:** `1≤L≤R≤10000`
**输入样例:**
```text
10 20
```
**输出样例:**
```text
11 13 17 19
4
```
**解题思路:** 外层枚举候选数,内层从 2 检查到平方根;找到因数立即 `break`
**易错提醒:** 1 不是素数;内层的 `break` 只结束因数检查,不会结束外层枚举。
---
## 课后作业
> 请完成本课对应的课后作业,见编程题文件。

View File

@@ -0,0 +1,198 @@
# 第2课循环结构 + 数位拆解
## 教学目标
- 掌握 for、while 和 do-while 循环的使用场景
- 掌握数位拆解技巧(取个位、去个位)
- 能用循环判断回文数
- 了解水仙花数的判断方法
## 核心考点:循环边界与数位拆解
### 考什么
常见题型要求重复执行固定次数、在条件成立时持续循环,或逐位处理一个整数,例如求各位和、反转数字、判断回文数。考点既包括循环写法,也包括循环何时开始、何时结束。
### 必须理解
`for` 适合次数明确的重复,`while` 适合结束条件更自然的过程。数位拆解每轮做三件事:`n % 10` 取个位,使用这个个位完成统计或拼接,`n /= 10` 去掉个位。因为 n 会不断变小,若后面还要与原数比较,必须提前备份。循环边界要保证每个需要处理的值恰好进入一次,不能漏掉终点或多执行一轮。
### 核心写法
```cpp
int original = n;
int reversed = 0;
while (n > 0) {
int digit = n % 10;
reversed = reversed * 10 + digit;
n /= 10;
}
bool isPalindrome = (reversed == original);
```
取位、使用、去位的顺序不能乱;`n /= 10` 还保证循环最终结束。
### `do-while` 循环
`do-while` 会先执行循环体,再判断条件,因此循环体至少执行一次。适合“先操作一次,再决定是否继续”的场景。
```cpp
int number;
do {
cin >> number;
} while (number != 0);
```
注意结尾的分号不能漏掉。三种循环选择原则:次数明确优先 `for`;先判断再重复常用 `while`;至少执行一次使用 `do-while`
### 常见错误
- 忘记备份原数,拆解结束后 n 已变成 0。
- 忘记更新循环变量,造成死循环。
-`i < n``i <= n` 混用,导致少算或多算一次。
- 反转数字时漏写 `reversed * 10`,无法给新个位腾出位置。
### 判断是否掌握
能判断一个任务该用 `for` 还是 `while`,能准确写出循环范围,并能独立完成取个位、去个位、求各位和或反转数字。
---
## 题目一:判断回文数
### 核心代码(一句话)
> 回文数的核心:**把数字反转,看反转后和原数是否相等。** 反转靠三行代码——取个位、拼新数、去个位。
### 自然语言思路
回文数就是"正着读反着读都一样"的数,比如 12321、4554、7。
**怎么判断?分三步走:**
**第一步:备份原数。** 因为待会儿要拆解它,拆完就没了,所以先用 `temp = n` 把原数存好。
**第二步:反转数字。** 这是核心!口诀:**"取个位、拼新数、去个位"**,循环直到原数变成 0。
以 123 为例,手推一遍:
| 轮次 | 原数 temp | 个位 d = temp%10 | 反转数 rev = rev×10+d | temp = temp/10 |
|------|-----------|-------------------|------------------------|-----------------|
| 第1轮 | 123 | 3 | 0×10+3 = **3** | 12 |
| 第2轮 | 12 | 2 | 3×10+2 = **32** | 1 |
| 第3轮 | 1 | 1 | 32×10+1 = **321** | 0 |
| 结束 | 0 | — | **321** | — |
反转 123 得到 321。这个过程就像把数字"倒着拼回去"。
**第三步:比较。** 原数 n备份的和反转数 rev 相等 → 回文数!
### 伪代码
```
输入 n
temp = n // 备份原数
rev = 0 // 反转数初始为0
当 temp > 0:
d = temp % 10 // 取个位
rev = rev * 10 + d // 拼到反转数的末尾
temp = temp / 10 // 去掉个位
如果 rev == n:
输出 "是回文数"
否则:
输出 "不是回文数"
```
### 真实代码
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int rev = 0; // 存放反转后的数
int temp = n; // 备份原数,因为要拆解
while (temp > 0) {
int d = temp % 10; // ① 取个位
rev = rev * 10 + d; // ② 拼到新数末尾
temp /= 10; // ③ 去掉个位
}
if (rev == n) {
cout << "是回文数" << endl;
} else {
cout << "不是回文数" << endl;
}
return 0;
}
```
### 注意点
1. **一定要备份原数**`temp = n` 这一步不能省!否则 n 被拆解成 0 了,最后没法比较。
2. **`while` vs `for`**:数位拆解用 `while` 更自然,因为事先不知道数字有几位。`for` 适用于知道循环次数的情况。
3. **`temp % 10` 取个位**:任何整数模 10 就得到个位。如 12345 % 10 = 5。
4. **`temp / 10` 去个位**:整数除法自动舍去余数。如 12345 / 10 = 1234。
5. **边界0 和负数**0 是回文数0反转还是0。负数一般不算回文数-121 反转是 121-GESP 一级不会考负数回文。
6. **循环终止条件**`temp > 0`,不是 `temp >= 0`(会导致死循环,因为 0/10 还是 0
---
## 题目二:(待橙子补充)
> 建议覆盖水仙花数、for循环基本用法等
---
### 核心代码(一句话)
>
### 自然语言思路
### 伪代码
### 真实代码
```cpp
```
### 注意点
---
## 题目三:(待橙子补充)
> 建议覆盖break/continue、循环综合练习等
---
### 核心代码(一句话)
>
### 自然语言思路
### 伪代码
### 真实代码
```cpp
```
### 注意点
---
## 本课打油诗
> **(数位拆解)**
> 循环取位倒着排,个位拼到新数来。
> 除十去尾继续转,正反相等回文哉。
> **(回文数判断)**
> 反转数字比原数,相等就是回文数。
> 备份原数不能忘,拆完再比才无误。
---
## 课后作业
(待补充)
1.
2.

View File

@@ -0,0 +1,690 @@
# 第5课编程题教师版
## 本课知识点覆盖表
| 核心知识点 | GESP等级 | 对应课堂题 |
|---|---|---|
| `for`、累加、累乘与循环边界 | 一级必考 | 课堂1 |
| `while`、取个位、去个位、数字反转 | 一级/二级 | 课堂2 |
| `break``continue` 与循环控制 | 一级必考 | 课堂3 |
| 双层循环、外层枚举对象、内层检查条件 | 一级巩固/二级 | 课堂4 |
> 本课包含 6 道双层循环题,作为第 6 课嵌套循环的过渡。
## 一、课堂练习4题
### 课堂1奇偶分组统计【适中】
**题目描述:** 输入正整数 `n`,分别求 `1n` 中奇数之和、偶数之和,并计算 `1n` 的乘积。若乘积超过 `10^18`,输出 `TOO LARGE`
**输入格式:** 输入一个正整数。
**输出格式:** 第一行奇数和与偶数和;第二行乘积或 `TOO LARGE`
**数据范围:** `1≤n≤30`
**输入样例:**
```text
5
```
**输出样例:**
```text
9 6
120
```
**解题思路:** 一个 `for` 循环同时完成奇偶累加;累乘前判断 `product>10^18/i`,避免溢出。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
long long oddSum = 0, evenSum = 0, product = 1;
bool tooLarge = false;
cin >> n;
for (int i = 1; i <= n; i++) {
if (i % 2 == 0) evenSum += i;
else oddSum += i;
if (!tooLarge) {
if (product > 1000000000000000000LL / i) tooLarge = true;
else product *= i;
}
}
cout << oddSum << " " << evenSum << "\n";
if (tooLarge) cout << "TOO LARGE";
else cout << product;
return 0;
}
```
**易错提醒:** 范围是 `1n`,循环条件必须为 `i<=n`;累加器和累乘器的初值分别为 0 和 1。
### 课堂2数字分析报告【适中】
**题目描述:** 输入一个非负整数,输出它的位数、各位数字之和、最大数字和反转数。
**输入格式:** 输入一个非负整数。
**输出格式:** 依次输出位数、各位和、最大数字、反转数。
**数据范围:** `0≤n≤10^9`
**输入样例:**
```text
50230
```
**输出样例:**
```text
5 10 5 3205
```
**解题思路:** 备份原数,用 `while` 重复“取个位、使用个位、去个位”;单独处理 `n=0`
**参考代码:**
```cpp
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n;
if (n == 0) {
cout << "1 0 0 0";
return 0;
}
int temp = n, count = 0, sum = 0, biggest = 0, reversed = 0;
while (temp > 0) {
int digit = temp % 10;
count++;
sum += digit;
biggest = max(biggest, digit);
reversed = reversed * 10 + digit;
temp /= 10;
}
cout << count << " " << sum << " " << biggest << " " << reversed;
return 0;
}
```
**易错提醒:** 反转后的前导零不会保留0 本身有一位,不能被普通循环漏掉。
### 课堂3寻找第一个合格数【适中】
**题目描述:**`1n` 中寻找第一个同时满足“能被 7 整除、不能被 5 整除、各位和大于 10”的数。跳过所有含数字 4 的数;找到后立即输出,否则输出 `NONE`
**输入格式:** 输入一个正整数。
**输出格式:** 第一个合格数或 `NONE`
**数据范围:** `1≤n≤100000`
**输入样例:**
```text
100
```
**输出样例:**
```text
77
```
**解题思路:** 外层枚举数字;拆位检查含 4 和各位和。含 4 时 `continue`,找到答案时 `break`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, answer = -1;
cin >> n;
for (int x = 1; x <= n; x++) {
int temp = x, sum = 0;
bool hasFour = false;
while (temp > 0) {
int digit = temp % 10;
if (digit == 4) hasFour = true;
sum += digit;
temp /= 10;
}
if (hasFour) continue;
if (x % 7 == 0 && x % 5 != 0 && sum > 10) {
answer = x;
break;
}
}
if (answer == -1) cout << "NONE";
else cout << answer;
return 0;
}
```
**易错提醒:** `continue` 跳过本轮,`break` 结束整个当前循环,两者含义不同。
### 课堂4区间素数统计【适中·双层循环】
**题目描述:** 输入 `L``R`,输出闭区间内的所有素数及素数个数。
**输入格式:** 输入区间的左端点和右端点。
**输出格式:** 第一行按升序输出素数;第二行输出个数。没有素数时第一行输出 `NONE`
**数据范围:** `1≤L≤R≤10000`
**输入样例:**
```text
10 20
```
**输出样例:**
```text
11 13 17 19
4
```
**解题思路:** 外层枚举候选数,内层从 2 检查到平方根;找到因数立即 `break`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int L, R, count = 0;
cin >> L >> R;
for (int x = L; x <= R; x++) {
if (x < 2) continue;
bool prime = true;
for (int d = 2; d * d <= x; d++) {
if (x % d == 0) {
prime = false;
break;
}
}
if (prime) {
if (count) cout << " ";
cout << x;
count++;
}
}
if (count == 0) cout << "NONE";
cout << "\n" << count;
return 0;
}
```
**易错提醒:** 1 不是素数;内层的 `break` 只结束因数检查,不会结束外层枚举。
## 二、课后作业3题
### 课后1从 1 加到 n【简单】
**题目描述:** 输入 `n`,用循环求 `1+2+…+n`
**输入格式:** 输入一个正整数。
**输出格式:** 累加和。
**数据范围:** `1≤n≤10^6`
**输入样例:**
```text
5
```
**输出样例:**
```text
15
```
**解题思路:** 累加器从 0 开始,循环范围为 `1n`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
long long sum = 0;
cin >> n;
for (int i = 1; i <= n; i++) sum += i;
cout << sum;
return 0;
}
```
**易错提醒:** 和可能超过 `int`,使用 `long long`
### 课后2各位数字之和【简单】
**题目描述:** 输入一个正整数,输出各位数字之和。
**输入格式:** 输入一个正整数。
**输出格式:** 各位数字之和。
**数据范围:** `1≤n≤10^9`
**输入样例:**
```text
5082
```
**输出样例:**
```text
15
```
**解题思路:** 每轮用 `n%10` 取个位并累加,再用 `n/=10` 去个位。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, sum = 0;
cin >> n;
while (n > 0) {
sum += n % 10;
n /= 10;
}
cout << sum;
return 0;
}
```
**易错提醒:** 循环内必须更新 `n`,否则会形成死循环。
### 课后3跳过 3 的倍数【简单】
**题目描述:** 输出 `1n` 中所有不是 3 的倍数的数。
**输入格式:** 输入一个正整数。
**输出格式:** 符合条件的数,以空格分隔。
**数据范围:** `1≤n≤100`
**输入样例:**
```text
7
```
**输出样例:**
```text
1 2 4 5 7
```
**解题思路:** 遇到 3 的倍数时使用 `continue`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
bool first = true;
cin >> n;
for (int i = 1; i <= n; i++) {
if (i % 3 == 0) continue;
if (!first) cout << " ";
cout << i;
first = false;
}
return 0;
}
```
**易错提醒:** 输出空格时用 `first` 控制,可避免行末多余空格。
## 三、扩展练习7题
### 扩展1回文数判断【基础综合】
**题目描述:** 输入一个非负整数,判断是否为回文数。
**输入格式:** 输入一个非负整数。
**输出格式:** 是回文数输出 `YES`,否则输出 `NO`
**数据范围:** `0≤n≤10^9`
**输入样例:**
```text
12321
```
**输出样例:**
```text
YES
```
**解题思路:** 备份原数,构造反转数并比较。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, temp, reversed = 0;
cin >> n;
temp = n;
while (temp > 0) {
reversed = reversed * 10 + temp % 10;
temp /= 10;
}
cout << (reversed == n ? "YES" : "NO");
return 0;
}
```
**易错提醒:** 0 的反转仍是 0因此程序也能正确判断。
### 扩展2三位水仙花数【中等·双层思维】
**题目描述:** 输出 `100n` 中所有水仙花数。三位数各位数字立方和等于原数时称为水仙花数。
**输入格式:** 输入一个整数,表示枚举范围的上界。
**输出格式:** 所有水仙花数,以空格分隔;没有则输出 `NONE`
**数据范围:** `100≤n≤999`
**输入样例:**
```text
400
```
**输出样例:**
```text
153 370 371
```
**解题思路:** 外层枚举三位数,内层 `while` 拆出三位并累加立方。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count = 0;
cin >> n;
for (int x = 100; x <= n; x++) {
int temp = x, sum = 0;
while (temp > 0) {
int d = temp % 10;
sum += d * d * d;
temp /= 10;
}
if (sum == x) {
if (count) cout << " ";
cout << x;
count++;
}
}
if (count == 0) cout << "NONE";
return 0;
}
```
**易错提醒:** 判断时要与原数 `x` 比较,不能与已变成 0 的 `temp` 比较。
### 扩展3因数清单【中等·双层循环】
**题目描述:**`1n` 中的每个数,输出该数及它的所有正因数,每个数占一行。
**输入格式:** 输入一个正整数。
**输出格式:**`x` 行先输出 `x:`,再输出 `x` 的所有正因数。
**数据范围:** `1≤n≤30`
**输入样例:**
```text
3
```
**输出样例:**
```text
1: 1
2: 1 2
3: 1 3
```
**解题思路:** 外层枚举数字 `x`,内层枚举可能的因数 `d`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int x = 1; x <= n; x++) {
cout << x << ":";
for (int d = 1; d <= x; d++) {
if (x % d == 0) cout << " " << d;
}
cout << "\n";
}
return 0;
}
```
**易错提醒:** 内层上界随当前数字 `x` 变化,不是固定使用 `n`
### 扩展4完数统计【中等·双层循环】
**题目描述:** 一个正整数的所有真因数(不含自身)之和等于它本身,则称为完数。输出 `2n` 中所有完数。
**输入格式:** 输入一个正整数。
**输出格式:** 所有完数;没有则输出 `NONE`
**数据范围:** `2≤n≤10000`
**输入样例:**
```text
30
```
**输出样例:**
```text
6 28
```
**解题思路:** 外层枚举候选数,内层枚举 `1x/2` 并累加因数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count = 0;
cin >> n;
for (int x = 2; x <= n; x++) {
int sum = 0;
for (int d = 1; d <= x / 2; d++) {
if (x % d == 0) sum += d;
}
if (sum == x) {
if (count) cout << " ";
cout << x;
count++;
}
}
if (count == 0) cout << "NONE";
return 0;
}
```
**易错提醒:** 真因数不包含自身;每次检查新数字时 `sum` 都要清零。
### 扩展5乘积等于目标的数对【中等·双层循环】
**题目描述:** 输入 `n` 和目标 `m`,输出所有满足 `1≤a<b≤n``a*b=m` 的数对。
**输入格式:** 输入枚举上界和目标乘积。
**输出格式:** 每行一对 `a b`;没有则输出 `NONE`
**数据范围:** `1≤n≤1000``1≤m≤10^6`
**输入样例:**
```text
10 24
```
**输出样例:**
```text
3 8
4 6
```
**解题思路:** 外层枚举 `a`,内层只从 `a+1` 枚举 `b`,避免重复和 `a=b`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, m, count = 0;
cin >> n >> m;
for (int a = 1; a <= n; a++) {
for (int b = a + 1; b <= n; b++) {
if (a * b == m) {
cout << a << " " << b << "\n";
count++;
}
}
}
if (count == 0) cout << "NONE";
return 0;
}
```
**易错提醒:** `b``a+1` 开始,才能保证每个无序数对只输出一次。
### 扩展6回文素数【略难·双层循环】
**题目描述:** 输出 `LR` 中既是回文数又是素数的数。
**输入格式:** 输入区间的左端点和右端点。
**输出格式:** 所有回文素数;没有则输出 `NONE`
**数据范围:** `1≤L≤R≤100000`
**输入样例:**
```text
10 150
```
**输出样例:**
```text
11 101 131
```
**解题思路:** 外层枚举;先反转判断回文,通过后再用内层循环判断素数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int L, R, count = 0;
cin >> L >> R;
for (int x = L; x <= R; x++) {
int temp = x, reversed = 0;
while (temp > 0) {
reversed = reversed * 10 + temp % 10;
temp /= 10;
}
if (reversed != x || x < 2) continue;
bool prime = true;
for (int d = 2; d * d <= x; d++) {
if (x % d == 0) {
prime = false;
break;
}
}
if (prime) {
if (count) cout << " ";
cout << x;
count++;
}
}
if (count == 0) cout << "NONE";
return 0;
}
```
**易错提醒:** 先判断回文可减少素数检查次数1 仍然不是素数。
### 扩展7最接近目标的因数积【略难·双层循环】
**题目描述:** 输入 `n` 和目标 `target`。在所有满足 `1≤a≤b≤n` 的数对中,寻找乘积与目标差的绝对值最小的一对;并列时先选较小的 `a`,再选较小的 `b`
**输入格式:** 输入数对中每个数的上界和目标值。
**输出格式:** 输出 `a b a*b`
**数据范围:** `1≤n≤500``1≤target≤250000`
**输入样例:**
```text
10 53
```
**输出样例:**
```text
6 9 54
```
**解题思路:** 双层枚举所有合法数对,用二次打擂台比较乘积距离、`a``b`
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n, target;
cin >> n >> target;
int bestA = 1, bestB = 1;
for (int a = 1; a <= n; a++) {
for (int b = a; b <= n; b++) {
int now = a * b;
int best = bestA * bestB;
if (abs(now - target) < abs(best - target) ||
(abs(now - target) == abs(best - target) && a < bestA) ||
(abs(now - target) == abs(best - target) &&
a == bestA && b < bestB)) {
bestA = a;
bestB = b;
}
}
}
cout << bestA << " " << bestB << " " << bestA * bestB;
return 0;
}
```
**易错提醒:** 枚举范围允许 `a=b`;并列规则必须按顺序完整处理。

View File

@@ -0,0 +1,151 @@
# 第3课自定义函数教学拓展
> 等级说明:本课用于拓展代码分解能力,不属于 GESP 一级、二级必考内容。
## 教学目标
- 掌握函数的定义、调用与声明
- 理解形参与实参、返回值、void函数
- 理解函数重载(同名不同参)
- 理解局部变量与全局变量的作用域
## 核心考点:函数的参数、返回值与调用过程
### 考什么
补全函数定义区分________与________并根据函数调用过程判断输出。
### 必须理解
定义函数时括号里的变量叫________调用时传入的值叫________。执行到 `return`函数会________________。普通值传递中形参得到的是实参的________。
### 核心写法
```cpp
int maxValue(int a, int b) {
if (a > b) return ________;
return ________;
}
int ans = maxValue(________, ________);
```
### 常见错误
- 返回类型不是 `void`却忘记________________
- 修改普通形参是否一定改变外部实参________________
- 局部变量可以在哪个范围使用________________
### 判断是否掌握
- [ ] 我能区分形参和实参。
- [ ] 我能说清函数调用与返回的执行顺序。
- [ ] 我能独立编写有参数、有返回值的函数。
---
## 课堂编程题
### 课堂1三个数的最大公约数【适中】
**题目描述:** 编写函数 `gcd(a,b)`,使用辗转相除法求两个正整数的最大公约数。在 `main` 前写函数声明、`main` 后写函数定义,连续调用两次求三个数的最大公约数。
**输入格式:** 输入三个正整数。
**输出格式:** 三个数的最大公约数。
**数据范围:** `1≤a,b,c≤10^9`
**输入样例:**
```text
12 18 30
```
**输出样例:**
```text
6
```
**解题思路:** 先求 `gcd(a,b)`,再把结果与 `c` 求最大公约数,体会函数声明、定义和多次调用。
**易错提醒:** 函数声明必须与定义的返回类型、函数名和参数列表一致。
---
### 课堂2统计区间内的素数【适中】
**题目描述:** 编写有参数、有返回值的函数 `isPrime(n)`。输入 `L``R`,输出闭区间 `[L,R]` 中素数的个数。
**输入格式:** 输入区间的左端点和右端点。
**输出格式:** 素数个数。
**数据范围:** `1≤L≤R≤10000`
**输入样例:**
```text
10 20
```
**输出样例:**
```text
4
```
**解题思路:** 每个实参传入函数后复制给形参 `n`;找到因数时提前返回 `false`,全部检查后返回 `true`
**易错提醒:** 1 不是素数;普通值传递不会修改主函数中的实参。
---
### 课堂3统一格式输出【适中】
**题目描述:** 重载函数 `printLine`:一个版本接收一个整数并输出由该整数重复 5 次组成的一行;另一个版本接收一个字符和次数,输出指定长度的一行。两个函数都没有返回值。
**输入格式:** 依次输入一个整数、一个字符和字符的重复次数。
**输出格式:** 第一行输出 5 个 `x`;第二行输出 `n` 个字符 `c`
**数据范围:** `0≤x≤9``1≤n≤50`
**输入样例:**
```text
7 * 4
```
**输出样例:**
```text
77777
****
```
**解题思路:** 两个函数同名但参数列表不同,构成函数重载;只负责输出,因此返回类型为 `void`
**易错提醒:** 重载依据参数数量或类型区分,不能只靠返回类型区分。
---
### 课堂4全班累计加分【适中】
**题目描述:** 全局变量 `total` 表示全班累计加分。编写 `addScore(x)` 修改它;函数内部再定义同名局部变量 `total`,演示局部变量优先。每次调用后输出全局累计值。
**输入格式:** 第一行输入加分次数;第二行输入每次的加分值。
**输出格式:** 输出每次加分后的全局累计值。
**数据范围:** `1≤n≤20`,每次加分 `110`
**输入样例:**
```text
3
2 5 1
```
**输出样例:**
```text
2 7 8
```
**解题思路:**`::total` 指明全局变量;局部变量仅在函数的大括号内有效。
**易错提醒:** 局部变量会遮住同名全局变量;实际编程应尽量避免故意重名,本题只用于理解作用域。
---
## 课后作业
1.
2.

View File

@@ -0,0 +1,171 @@
# 第3课自定义函数教学拓展
> 等级说明:自定义函数属于 GESP 四级知识。本课程保留这一课,是为了训练代码分解和复用能力,不作为一级、二级考试必考内容。
## 教学目标
- 掌握函数的定义、调用与声明
- 理解形参与实参、返回值、void函数
- 理解函数重载(同名不同参)
- 理解局部变量与全局变量的作用域
## 核心考点:函数的参数、返回值与调用过程
### 考什么
常见题型包括补全函数定义、判断形参与实参、根据调用过程求输出,以及把一段重复逻辑封装成函数。重点是看懂数据怎样进入函数、函数怎样处理、结果怎样返回主程序。
### 必须理解
定义函数时括号里的变量叫形参,是函数内部接收数据的“临时名字”;调用函数时传入的值叫实参。普通值传递会把实参复制给形参,函数内部修改形参通常不会改变外面的变量。执行到 `return` 时,函数立即结束并把结果交回调用位置;`void` 函数不返回结果。局部变量只在所属的大括号内有效,不同函数中的同名局部变量互不影响。
### 核心写法
```cpp
int maxValue(int a, int b) { // a、b 是形参
if (a > b) return a;
return b;
}
int answer = maxValue(x, y); // x、y 是实参
```
调用时先计算实参,再进入函数执行;遇到 `return` 后回到原调用位置,把返回值赋给 `answer`
### 常见错误
- 声明的返回类型是 `int`,却忘记写 `return`
- 把函数定义写进 `main` 内部C++ 不允许在函数内部再定义普通函数。
- 以为修改普通形参就会修改实参,混淆“值的复制”和“原变量”。
- 在变量作用域之外使用局部变量。
### 判断是否掌握
能画出“实参传入→形参接收→函数执行→return 返回→主程序继续”的过程,并能独立编写一个有参数、有返回值的判断或计算函数。
---
## 题目一:判断素数
### 核心代码(一句话)
> 判断素数的核心:**从 2 试到 √n只要有一个能整除就不是素数。**
### 自然语言思路
素数就是"除了1和自己不能被任何数整除"的数。
比如判断 17 是不是素数:
- 试 217 ÷ 2 = 8 余 1不行
- 试 317 ÷ 3 = 5 余 2不行
- 试 417 ÷ 4 = 4 余 1不行
- 试到 √17 ≈ 4.1,所以只需试到 4
- 全都不行 → 17 是素数!
为什么只试到 **√n**?因为如果 n 有一个大于 √n 的因数,那一定会有一个小于 √n 的因数跟它配对。比如 36 = 6 × 6一个因数大于 6另一个必然小于 6。所以试到 √n 就够了。
**为什么要写成函数?** 判断素数的代码以后经常用到,封装成 `isPrime(n)`,以后需要时一行调用,不用重复写。
### 伪代码
```
函数 isPrime(n):
如果 n < 2: 返回 false // 1 不是素数
对于 i 从 2 到 sqrt(n):
如果 n % i == 0: // 能被整除
返回 false // 不是素数,直接返回
返回 true // 全都试过了,是素数
主程序:
输入 m
如果 isPrime(m): 输出 "YES"
否则: 输出 "NO"
```
### 真实代码
```cpp
#include <iostream>
#include <cmath>
using namespace std;
// 函数定义:判断素数
bool isPrime(int n) {
if (n < 2) return false; // 1 和负数不是素数
for (int i = 2; i <= sqrt(n); i++) {
if (n % i == 0) return false; // 找到因数,不是素数
}
return true; // 全部试完,是素数
}
int main() {
int m;
cin >> m;
if (isPrime(m)) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
return 0;
}
```
### 注意点
1. **1 不是素数**:必须在函数开头处理 `n < 2` 的情况,这是 GESP 高频易错点。
2. **为什么 `i <= sqrt(n)` 而不是 `i < n`**:前者效率高很多。判断 100 是否是素数,试到 10 就够了,不用试到 99。
3. **`sqrt` 返回 double**`i <= sqrt(n)` 是 int 和 double 比较C++ 会自动转换,没问题。但如果担心精度,可以写成 `i * i <= n`
4. **函数名要有意义**`isPrime` 一看就知道是"判断素数",比 `f``check` 好得多。
5. **局部变量 i**i 只在 isPrime 函数里有效main 里访问不到它 —— 这就是**局部变量**的作用域。
---
## 题目二:(待橙子补充)
> 建议覆盖函数重载、void函数等
---
### 核心代码(一句话)
>
### 自然语言思路
### 伪代码
### 真实代码
```cpp
```
### 注意点
---
## 题目三:(待橙子补充)
> 建议覆盖:局部变量与全局变量对比
---
### 核心代码(一句话)
>
### 自然语言思路
### 伪代码
### 真实代码
```cpp
```
### 注意点
---
## 本课打油诗
> 函数写好存身旁,起名调用响当当。
> 参数传进返结果,少写重复心不慌。
---
## 课后作业
(待补充)
1.
2.

View File

@@ -0,0 +1,687 @@
# 第2课编程题教师版
## 本课知识点覆盖表
| 核心知识点 | GESP等级 | 对应课堂题 |
|---|---|---|
| 函数声明、定义与调用 | 二级必考 | 课堂1 |
| 形参与实参、值传递、返回值、提前 `return` | 二级必考 | 课堂2 |
| `void` 函数与函数重载 | 二级 | 课堂3 |
| 局部变量、全局变量与作用域 | 二级必考 | 课堂4 |
> 难度均为本课内部相对难度,参考程序采用 C++17。
## 一、课堂练习4题
### 课堂1三个数的最大公约数【适中】
**题目描述:** 编写函数 `gcd(a,b)`,使用辗转相除法求两个正整数的最大公约数。在 `main` 前写函数声明、`main` 后写函数定义,连续调用两次求三个数的最大公约数。
**输入格式:** 输入三个正整数。
**输出格式:** 三个数的最大公约数。
**数据范围:** `1≤a,b,c≤10^9`
**输入样例:**
```text
12 18 30
```
**输出样例:**
```text
6
```
**解题思路:** 先求 `gcd(a,b)`,再把结果与 `c` 求最大公约数,体会函数声明、定义和多次调用。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int gcd(int a, int b);
int main() {
int a, b, c;
cin >> a >> b >> c;
cout << gcd(gcd(a, b), c);
return 0;
}
int gcd(int a, int b) {
while (b != 0) {
int r = a % b;
a = b;
b = r;
}
return a;
}
```
**易错提醒:** 函数声明必须与定义的返回类型、函数名和参数列表一致。
### 课堂2统计区间内的素数【适中】
**题目描述:** 编写有参数、有返回值的函数 `isPrime(n)`。输入 `L``R`,输出闭区间 `[L,R]` 中素数的个数。
**输入格式:** 输入区间的左端点和右端点。
**输出格式:** 素数个数。
**数据范围:** `1≤L≤R≤10000`
**输入样例:**
```text
10 20
```
**输出样例:**
```text
4
```
**解题思路:** 每个实参传入函数后复制给形参 `n`;找到因数时提前返回 `false`,全部检查后返回 `true`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
int main() {
int L, R, count = 0;
cin >> L >> R;
for (int x = L; x <= R; x++) {
if (isPrime(x)) count++;
}
cout << count;
return 0;
}
```
**易错提醒:** 1 不是素数;普通值传递不会修改主函数中的实参。
### 课堂3统一格式输出【适中】
**题目描述:** 重载函数 `printLine`:一个版本接收一个整数并输出由该整数重复 5 次组成的一行;另一个版本接收一个字符和次数,输出指定长度的一行。两个函数都没有返回值。
**输入格式:** 依次输入一个整数、一个字符和字符的重复次数。
**输出格式:** 第一行输出 5 个 `x`;第二行输出 `n` 个字符 `c`
**数据范围:** `0≤x≤9``1≤n≤50`
**输入样例:**
```text
7 * 4
```
**输出样例:**
```text
77777
****
```
**解题思路:** 两个函数同名但参数列表不同,构成函数重载;只负责输出,因此返回类型为 `void`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
void printLine(int x) {
for (int i = 0; i < 5; i++) cout << x;
cout << "\n";
}
void printLine(char c, int n) {
for (int i = 0; i < n; i++) cout << c;
cout << "\n";
}
int main() {
int x, n;
char c;
cin >> x >> c >> n;
printLine(x);
printLine(c, n);
return 0;
}
```
**易错提醒:** 重载依据参数数量或类型区分,不能只靠返回类型区分。
### 课堂4全班累计加分【适中】
**题目描述:** 全局变量 `total` 表示全班累计加分。编写 `addScore(x)` 修改它;函数内部再定义同名局部变量 `total`,演示局部变量优先。每次调用后输出全局累计值。
**输入格式:** 第一行输入加分次数;第二行输入每次的加分值。
**输出格式:** 输出每次加分后的全局累计值。
**数据范围:** `1≤n≤20`,每次加分 `110`
**输入样例:**
```text
3
2 5 1
```
**输出样例:**
```text
2 7 8
```
**解题思路:**`::total` 指明全局变量;局部变量仅在函数的大括号内有效。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int total = 0;
void addScore(int x) {
int total = x;
::total += total;
}
int main() {
int n, x;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> x;
addScore(x);
if (i) cout << " ";
cout << total;
}
return 0;
}
```
**易错提醒:** 局部变量会遮住同名全局变量;实际编程应尽量避免故意重名,本题只用于理解作用域。
## 二、课后作业3题
### 课后1求长方形面积【简单】
**题目描述:** 编写函数 `area(a,b)` 返回长方形面积。
**输入格式:** 输入两个正整数。
**输出格式:** 长方形面积。
**数据范围:** `1≤a,b≤10000`
**输入样例:**
```text
4 6
```
**输出样例:**
```text
24
```
**解题思路:** 函数接收两个形参,返回它们的乘积。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int area(int a, int b) {
return a * b;
}
int main() {
int a, b;
cin >> a >> b;
cout << area(a, b);
return 0;
}
```
**易错提醒:** 有返回值的函数不能漏写 `return`
### 课后2判断偶数【简单】
**题目描述:** 编写布尔函数 `isEven(n)`,偶数返回 `true`,奇数返回 `false`。主函数据此输出 `YES``NO`
**输入格式:** 输入一个整数。
**输出格式:** `YES``NO`
**数据范围:** `-10^9≤n≤10^9`
**输入样例:**
```text
18
```
**输出样例:**
```text
YES
```
**解题思路:** 判断 `n%2==0` 并直接返回结果。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
bool isEven(int n) {
return n % 2 == 0;
}
int main() {
int n;
cin >> n;
cout << (isEven(n) ? "YES" : "NO");
return 0;
}
```
**易错提醒:** 判断相等使用 `==`,不是赋值符号 `=`
### 课后3打印分隔线【简单】
**题目描述:** 编写无返回值函数 `printLine(n)`,输出 `n` 个减号。
**输入格式:** 输入一个整数。
**输出格式:** 一行 `n``-`
**数据范围:** `1≤n≤100`
**输入样例:**
```text
5
```
**输出样例:**
```text
-----
```
**解题思路:** 输出操作封装进 `void` 函数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
void printLine(int n) {
for (int i = 0; i < n; i++) cout << "-";
}
int main() {
int n;
cin >> n;
printLine(n);
return 0;
}
```
**易错提醒:** `void` 函数不能把调用结果赋给变量。
## 三、扩展练习7题
### 扩展1绝对值函数【基础综合】
**题目描述:** 不使用标准库 `abs`,编写函数 `myAbs(n)` 返回整数的绝对值。
**输入格式:** 输入一个整数。
**输出格式:** `n` 的绝对值。
**数据范围:** `-10^9≤n≤10^9`
**输入样例:**
```text
-23
```
**输出样例:**
```text
23
```
**解题思路:** 负数返回 `-n`,否则返回 `n`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int myAbs(int n) {
if (n < 0) return -n;
return n;
}
int main() {
int n;
cin >> n;
cout << myAbs(n);
return 0;
}
```
**易错提醒:** 每条可能的执行路线都应有返回值。
### 扩展2数字各位和【基础综合】
**题目描述:** 编写函数 `digitSum(n)`,返回正整数各位数字之和。
**输入格式:** 输入一个正整数。
**输出格式:** 各位数字之和。
**数据范围:** `1≤n≤10^9`
**输入样例:**
```text
5026
```
**输出样例:**
```text
13
```
**解题思路:** 在函数内循环取个位并累加。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int digitSum(int n) {
int sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
return sum;
}
int main() {
int n;
cin >> n;
cout << digitSum(n);
return 0;
}
```
**易错提醒:** 函数内修改形参 `n` 不会改变主函数中的实参。
### 扩展3最小公倍数【中等】
**题目描述:** 编写 `gcd(a,b)``lcm(a,b)` 两个函数,输出两数的最小公倍数。
**输入格式:** 输入两个正整数。
**输出格式:** 最小公倍数。
**数据范围:** `1≤a,b≤10^6`,答案不超过 `10^9`
**输入样例:**
```text
12 18
```
**输出样例:**
```text
36
```
**解题思路:** 先求最大公约数,再利用 `lcm=a/gcd*b`,体现函数之间相互调用。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int gcd(int a, int b) {
while (b != 0) {
int r = a % b;
a = b;
b = r;
}
return a;
}
long long lcm(int a, int b) {
return 1LL * a / gcd(a, b) * b;
}
int main() {
int a, b;
cin >> a >> b;
cout << lcm(a, b);
return 0;
}
```
**易错提醒:** 先除后乘并使用 `long long`,可降低中间结果溢出的风险。
### 扩展4重载求面积【中等】
**题目描述:** 重载函数 `area`:接收一个整数时返回正方形面积;接收两个整数时返回长方形面积。输入类型编号及边长后输出面积。
**输入格式:** 先输入图形类型;类型为 1 时再输入一个边长,类型为 2 时再输入长和宽。
**输出格式:** 图形面积。
**数据范围:** 边长为 `110000`
**输入样例:**
```text
2 4 7
```
**输出样例:**
```text
28
```
**解题思路:** 用不同参数个数区分两个同名函数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int area(int side) {
return side * side;
}
int area(int length, int width) {
return length * width;
}
int main() {
int type, a, b;
cin >> type >> a;
if (type == 1) cout << area(a);
else {
cin >> b;
cout << area(a, b);
}
return 0;
}
```
**易错提醒:** 两个重载版本不能拥有完全相同的参数列表。
### 扩展5回文数函数【中等】
**题目描述:** 编写函数 `isPalindrome(n)` 判断正整数是否为回文数,输出 `YES``NO`
**输入格式:** 输入一个正整数。
**输出格式:** `YES``NO`
**数据范围:** `1≤n≤10^9`
**输入样例:**
```text
12321
```
**输出样例:**
```text
YES
```
**解题思路:** 在函数中备份原数并构造反转数,最后比较。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
bool isPalindrome(int n) {
int original = n;
int reversed = 0;
while (n > 0) {
reversed = reversed * 10 + n % 10;
n /= 10;
}
return reversed == original;
}
int main() {
int n;
cin >> n;
cout << (isPalindrome(n) ? "YES" : "NO");
return 0;
}
```
**易错提醒:** 必须备份原数;形参被拆完后会变成 0。
### 扩展6连续素数对【略难】
**题目描述:** 两个相差 2 的素数称为孪生素数。编写 `isPrime`,输出 `[2,n]` 中所有孪生素数对,每对占一行;若没有,输出 `NONE`
**输入格式:** 输入一个整数。
**输出格式:** 所有满足条件的 `p p+2`
**数据范围:** `2≤n≤100000`
**输入样例:**
```text
15
```
**输出样例:**
```text
3 5
5 7
11 13
```
**解题思路:** 枚举较小的数 `p`,同时调用两次 `isPrime`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
int main() {
int n;
bool found = false;
cin >> n;
for (int p = 2; p + 2 <= n; p++) {
if (isPrime(p) && isPrime(p + 2)) {
cout << p << " " << p + 2 << "\n";
found = true;
}
}
if (!found) cout << "NONE";
return 0;
}
```
**易错提醒:** 枚举上界必须保证 `p+2≤n`;无解情况不能漏掉。
### 扩展7函数流水线【略难】
**题目描述:** 编写三个函数:`digitSum(n)` 求各位和,`isPrime(n)` 判断素数,`countGood(L,R)` 统计区间中“各位和为素数”的整数个数。
**输入格式:** 输入区间的左端点和右端点。
**输出格式:** 满足条件的整数个数。
**数据范围:** `1≤L≤R≤100000`
**输入样例:**
```text
10 15
```
**输出样例:**
```text
3
```
**解题思路:** 1015 的各位和依次为 16其中 2、3、5 是素数。让 `countGood` 调用 `digitSum`,再把结果交给 `isPrime`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int digitSum(int n) {
int sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
return sum;
}
bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
int countGood(int L, int R) {
int count = 0;
for (int x = L; x <= R; x++) {
if (isPrime(digitSum(x))) count++;
}
return count;
}
int main() {
int L, R;
cin >> L >> R;
cout << countGood(L, R);
return 0;
}
```
**易错提醒:** 分清每个函数的单一职责;不要在多个位置重复编写同一段逻辑。

View File

@@ -0,0 +1,681 @@
# 第4课编程题教师版
## 本课知识点覆盖表
| 核心知识点 | GESP等级 | 对应课堂题 |
|---|---|---|
| `if / else if / else` 多层互斥分支 | 一级必考 | 课堂1 |
| 嵌套 `if`、逻辑运算与条件分组 | 一级必考 | 课堂2 |
| 普通打擂台、合法初始擂主 | 一级/二级 | 课堂3 |
| 二次打擂台、主条件与并列规则 | 二级 | 课堂4 |
> 本课重点训练多层分支结构。14 题中有 10 题需要三层以上分支或嵌套判断。
## 一、课堂练习4题
### 课堂1阶梯电费【适中】
**题目描述:** 某月用电量不超过 180 度的部分每度 0.50 元181280 度的部分每度 0.60 元;超过 280 度的部分每度 0.80 元。若总电费超过 200 元再加收 5% 服务费。输出应付金额。
**输入格式:** 输入一个非负整数,表示本月用电量。
**输出格式:** 应付金额,保留两位小数。
**数据范围:** `0≤n≤10000`
**输入样例:**
```text
300
```
**输出样例:**
```text
166.00
```
**解题思路:** 用多层 `if-else if-else` 分段累计;计算完成后再独立判断是否加收服务费。
**参考代码:**
```cpp
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
int n;
cin >> n;
double cost;
if (n <= 180) {
cost = n * 0.50;
} else if (n <= 280) {
cost = 180 * 0.50 + (n - 180) * 0.60;
} else {
cost = 180 * 0.50 + 100 * 0.60 + (n - 280) * 0.80;
}
if (cost > 200) cost *= 1.05;
cout << fixed << setprecision(2) << cost;
return 0;
}
```
**易错提醒:** 阶梯收费是分段累计,不是把全部用电量直接乘最高档单价。
### 课堂2游乐园购票方案【适中】
**题目描述:** 输入年龄、身高和是否有成人陪同。身高低于 120 厘米免费;否则年龄不满 12 岁时,有成人陪同票价 30 元无陪同不得入园1259 岁票价 80 元60 岁及以上票价 40 元。周末所有非免费票加收 10 元。
**输入格式:** 依次输入年龄、身高、是否有成人陪同和是否为周末;后两项用 0 或 1 表示。
**输出格式:** 免费输出 `FREE`,不得入园输出 `DENIED`,否则输出票价。
**数据范围:** `1≤age≤120``50≤height≤220`
**输入样例:**
```text
10 135 1 1
```
**输出样例:**
```text
40
```
**解题思路:** 先判断身高;不免费时再按年龄嵌套判断,儿童分支中继续判断陪同;最后对合法票价判断周末加价。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int age, height, accompanied, weekend;
cin >> age >> height >> accompanied >> weekend;
if (height < 120) {
cout << "FREE";
} else {
int price;
if (age < 12) {
if (accompanied == 0) {
cout << "DENIED";
return 0;
}
price = 30;
} else if (age < 60) {
price = 80;
} else {
price = 40;
}
if (weekend == 1) price += 10;
cout << price;
}
return 0;
}
```
**易错提醒:** “不得入园”不是票价 0输出后应立即结束避免继续加价。
### 课堂3最佳选手【适中】
**题目描述:** 输入 `n` 名选手的分数,输出最高分和第一次取得该分数的选手编号。
**输入格式:** 第一行输入选手人数;第二行输入每名选手的分数。
**输出格式:** 最高分和选手编号(从 1 开始)。
**数据范围:** `1≤n≤100`,分数为 `0100`
**输入样例:**
```text
5
88 95 90 95 70
```
**输出样例:**
```text
95 2
```
**解题思路:** 用第一名选手初始化擂主;只有出现严格更高分时才更新,便可保留最早编号。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, score, bestScore, bestId = 1;
cin >> n >> bestScore;
for (int id = 2; id <= n; id++) {
cin >> score;
if (score > bestScore) {
bestScore = score;
bestId = id;
}
}
cout << bestScore << " " << bestId;
return 0;
}
```
**易错提醒:** 擂主必须用第一个合法数据初始化;相等时不更新才能保留第一次出现的位置。
### 课堂4最接近目标的候选人【适中】
**题目描述:** 输入目标分 `target` 以及 `n` 名候选人的编号和分数。选择与目标分距离最小者;距离相同时选分数较高者;仍相同则选编号较小者。
**输入格式:** 第一行输入候选人数和目标分;随后每行输入一名候选人的编号和分数。
**输出格式:** 入选者编号和分数。
**数据范围:** `1≤n≤100`,编号互不相同,分数为 `0100`
**输入样例:**
```text
4 80
3 76
8 84
2 84
5 90
```
**输出样例:**
```text
2 84
```
**解题思路:** 依次比较距离、分数、编号,三层优先级构成完整更新条件。
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n, target, id, score, bestId, bestScore;
cin >> n >> target;
cin >> bestId >> bestScore;
for (int i = 1; i < n; i++) {
cin >> id >> score;
int d = abs(score - target);
int bestD = abs(bestScore - target);
if (d < bestD ||
(d == bestD && score > bestScore) ||
(d == bestD && score == bestScore && id < bestId)) {
bestId = id;
bestScore = score;
}
}
cout << bestId << " " << bestScore;
return 0;
}
```
**易错提醒:** 三层规则按先后顺序连接,每个次级规则都只能在前级相等时生效。
## 二、课后作业3题
### 课后1成绩等级【简单】
**题目描述:** 输入成绩90100 为 `A`8089 为 `B`7079 为 `C`6069 为 `D`059 为 `E`;范围外输出 `ERROR`
**输入格式:** 一个整数成绩。
**输出格式:** 等级或 `ERROR`
**数据范围:** 输入整数绝对值不超过 1000。
**输入样例:**
```text
86
```
**输出样例:**
```text
B
```
**解题思路:** 先排除非法范围,再从高到低使用多层 `else if`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int score;
cin >> score;
if (score < 0 || score > 100) cout << "ERROR";
else if (score >= 90) cout << "A";
else if (score >= 80) cout << "B";
else if (score >= 70) cout << "C";
else if (score >= 60) cout << "D";
else cout << "E";
return 0;
}
```
**易错提醒:** 从高到低判断后,不需要重复写上界。
### 课后2月份天数【简单】
**题目描述:** 输入年份和月份,输出该月天数。闰年规则:能被 400 整除,或能被 4 整除但不能被 100 整除。
**输入格式:** 输入年份和月份。
**输出格式:** 天数;月份非法输出 `ERROR`
**数据范围:** `1≤year≤9999`,月份可能为任意整数。
**输入样例:**
```text
2024 2
```
**输出样例:**
```text
29
```
**解题思路:** 先验证月份,再判断 2 月2 月内部嵌套判断闰年,其余月份判断 30 天或 31 天。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int year, month;
cin >> year >> month;
if (month < 1 || month > 12) {
cout << "ERROR";
} else if (month == 2) {
bool leap = year % 400 == 0 ||
(year % 4 == 0 && year % 100 != 0);
cout << (leap ? 29 : 28);
} else if (month == 4 || month == 6 ||
month == 9 || month == 11) {
cout << 30;
} else {
cout << 31;
}
return 0;
}
```
**易错提醒:** 闰年条件中的括号不能省略概念上的分组。
### 课后3简单计算器【简单】
**题目描述:** 输入两个整数和运算符 `+ - * / %`,输出计算结果。除数为 0 时输出 `ERROR`,未知运算符输出 `UNKNOWN`
**输入格式:** 依次输入第一个整数、运算符和第二个整数。
**输出格式:** 计算结果或错误信息。
**数据范围:** 两数绝对值不超过 10000。
**输入样例:**
```text
17 % 5
```
**输出样例:**
```text
2
```
**解题思路:** 先按运算符多层分支;进入除法或取模分支后再嵌套判断除数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int a, b;
char op;
cin >> a >> op >> b;
if (op == '+') cout << a + b;
else if (op == '-') cout << a - b;
else if (op == '*') cout << a * b;
else if (op == '/' || op == '%') {
if (b == 0) cout << "ERROR";
else if (op == '/') cout << a / b;
else cout << a % b;
} else {
cout << "UNKNOWN";
}
return 0;
}
```
**易错提醒:** 除法和取模都必须先检查除数是否为 0。
## 三、扩展练习7题
### 扩展1体质指数评价【基础综合】
**题目描述:** 输入体重(千克)和身高(米),计算 BMI。低于 18.5 输出 `LOW`18.523.9 输出 `NORMAL`2427.9 输出 `HIGH`28 及以上输出 `VERY HIGH`
**输入格式:** 输入体重和身高两个实数。
**输出格式:** BMI 等级。
**数据范围:** `20≤weight≤300``1≤height≤2.5`
**输入样例:**
```text
60 1.70
```
**输出样例:**
```text
NORMAL
```
**解题思路:** 先计算 `weight/(height*height)`,再按从小到大的边界进行多层判断。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
double weight, height;
cin >> weight >> height;
double bmi = weight / (height * height);
if (bmi < 18.5) cout << "LOW";
else if (bmi < 24) cout << "NORMAL";
else if (bmi < 28) cout << "HIGH";
else cout << "VERY HIGH";
return 0;
}
```
**易错提醒:** 身高要平方;连续区间判断必须保证没有空档或重叠。
### 扩展2三角形分类【中等】
**题目描述:** 输入三条边。不能组成三角形输出 `INVALID`;否则先按边分类输出 `EQUILATERAL``ISOSCELES``SCALENE`,再输出是否为直角三角形 `RIGHT``NOT RIGHT`
**输入格式:** 输入三个正整数,表示三条边长。
**输出格式:** 两行分类结果。
**数据范围:** 边长不超过 10000。
**输入样例:**
```text
3 4 5
```
**输出样例:**
```text
SCALENE
RIGHT
```
**解题思路:** 先判断合法性;合法后分别进行边型判断和直角判断。直角判断需覆盖三条边分别为斜边的情况。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
long long a, b, c;
cin >> a >> b >> c;
if (a + b <= c || a + c <= b || b + c <= a) {
cout << "INVALID";
return 0;
}
if (a == b && b == c) cout << "EQUILATERAL\n";
else if (a == b || a == c || b == c) cout << "ISOSCELES\n";
else cout << "SCALENE\n";
if (a * a + b * b == c * c ||
a * a + c * c == b * b ||
b * b + c * c == a * a) cout << "RIGHT";
else cout << "NOT RIGHT";
return 0;
}
```
**易错提醒:** 必须先判断能否组成三角形;平方可能较大,应使用 `long long`
### 扩展3快递计费【中等】
**题目描述:** 基础运费按重量计算:不超过 1 千克 8 元15 千克每增加 1 千克加 3 元,不足 1 千克按 1 千克算;超过 5 千克的部分每千克加 5 元。偏远地区加 10 元,加急再加总价的 20%。
**输入格式:** 依次输入包裹重量、是否为偏远地区和是否加急;后两项用 0 或 1 表示。
**输出格式:** 运费,保留两位小数。
**数据范围:** `0<w≤100`
**输入样例:**
```text
6.2 1 1
```
**输出样例:**
```text
48.00
```
**解题思路:** 先向上取整重量并按档位计算,再依次处理偏远和加急条件。
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
int main() {
double w;
int remote, urgent;
cin >> w >> remote >> urgent;
int kg = (int)ceil(w);
double cost;
if (kg <= 1) cost = 8;
else if (kg <= 5) cost = 8 + (kg - 1) * 3;
else cost = 8 + 4 * 3 + (kg - 5) * 5;
if (remote) cost += 10;
if (urgent) cost *= 1.2;
cout << fixed << setprecision(2) << cost;
return 0;
}
```
**易错提醒:** 示例中 6.2 千克按 7 千克计算;加急费在偏远附加费之后计算。
### 扩展4石头剪刀布裁判【中等】
**题目描述:** 输入两名选手的手势 `R`(石头)、`S`(剪刀)、`P`(布)。非法手势输出 `ERROR`;相同输出 `DRAW`;否则输出 `A``B`
**输入格式:** 两个字符。
**输出格式:** 裁判结果。
**数据范围:** 任意可见字符。
**输入样例:**
```text
R S
```
**输出样例:**
```text
A
```
**解题思路:** 先检查两个手势是否合法;再判断平局;最后用三个获胜组合判断 A 是否获胜。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
char a, b;
cin >> a >> b;
bool validA = a == 'R' || a == 'S' || a == 'P';
bool validB = b == 'R' || b == 'S' || b == 'P';
if (!validA || !validB) cout << "ERROR";
else if (a == b) cout << "DRAW";
else if ((a == 'R' && b == 'S') ||
(a == 'S' && b == 'P') ||
(a == 'P' && b == 'R')) cout << "A";
else cout << "B";
return 0;
}
```
**易错提醒:** 合法性判断必须放在胜负判断之前。
### 扩展5奖学金评定【中等】
**题目描述:** 输入平均分、竞赛获奖次数、志愿服务时长和是否违纪。违纪直接输出 `NONE`否则平均分≥90 且获奖≥2 为一等奖平均分≥85 且获奖≥1 或服务≥50为二等奖平均分≥80 或服务≥100 为三等奖,其余无奖。
**输入格式:** 依次输入平均分、获奖次数、志愿服务时长和是否违纪。
**输出格式:** `FIRST``SECOND``THIRD``NONE`
**数据范围:** 平均分 `0100`,其余为非负整数,违纪用 0/1 表示。
**输入样例:**
```text
87 0 60 0
```
**输出样例:**
```text
SECOND
```
**解题思路:** 违纪是最高优先级;未违纪时按奖项从高到低判断,括号明确“且”“或”的组合。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int average, awards, service, violation;
cin >> average >> awards >> service >> violation;
if (violation) cout << "NONE";
else if (average >= 90 && awards >= 2) cout << "FIRST";
else if (average >= 85 && (awards >= 1 || service >= 50))
cout << "SECOND";
else if (average >= 80 || service >= 100) cout << "THIRD";
else cout << "NONE";
return 0;
}
```
**易错提醒:** 一定要从高等级奖项向低等级判断,否则较宽条件会抢先匹配。
### 扩展6最佳商品【略难】
**题目描述:** 输入 `n` 件商品的编号、价格和评分。选择评分最高的商品;评分相同时价格更低者优先;仍相同则编号更小者优先。
**输入格式:** 第一行输入商品数量;随后每行输入一件商品的编号、价格和评分。
**输出格式:** 最佳商品编号。
**数据范围:** `1≤n≤100`,价格和评分为正整数。
**输入样例:**
```text
4
5 100 9
2 120 9
8 80 8
3 100 9
```
**输出样例:**
```text
3
```
**解题思路:** 第一优先级比较评分,第二优先级比较价格,第三优先级比较编号。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, id, price, score;
int bestId, bestPrice, bestScore;
cin >> n >> bestId >> bestPrice >> bestScore;
for (int i = 1; i < n; i++) {
cin >> id >> price >> score;
if (score > bestScore ||
(score == bestScore && price < bestPrice) ||
(score == bestScore && price == bestPrice && id < bestId)) {
bestId = id;
bestPrice = price;
bestScore = score;
}
}
cout << bestId;
return 0;
}
```
**易错提醒:** 不要用任意极值初始化三个擂台变量,直接读入第一件商品最安全。
### 扩展7综合录取规则【略难】
**题目描述:** 输入笔试、面试、体育三项成绩,以及是否有竞赛一等奖。任一成绩不在 0100 输出 `ERROR`。竞赛一等奖且笔试≥70 可直接录取否则笔试≥85 时面试≥80 且体育≥60 录取;笔试 7584 时面试≥90 且体育≥70 才录取;其他情况不录取。
**输入格式:** 依次输入笔试成绩、面试成绩、体育成绩和是否获得竞赛一等奖;最后一项用 0 或 1 表示。
**输出格式:** `ADMITTED``REJECTED``ERROR`
**数据范围:** 成绩可能为任意整数。
**输入样例:**
```text
78 92 75 0
```
**输出样例:**
```text
ADMITTED
```
**解题思路:** 先做数据合法性检查,再判断特殊录取,最后按笔试档位嵌套判断面试和体育条件。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int written, interview, sport, prize;
cin >> written >> interview >> sport >> prize;
if (written < 0 || written > 100 ||
interview < 0 || interview > 100 ||
sport < 0 || sport > 100) {
cout << "ERROR";
} else if (prize == 1 && written >= 70) {
cout << "ADMITTED";
} else if (written >= 85) {
if (interview >= 80 && sport >= 60) cout << "ADMITTED";
else cout << "REJECTED";
} else if (written >= 75) {
if (interview >= 90 && sport >= 70) cout << "ADMITTED";
else cout << "REJECTED";
} else {
cout << "REJECTED";
}
return 0;
}
```
**易错提醒:** 数据检查、特殊规则、普通规则的优先级不能调换。

View File

@@ -0,0 +1,289 @@
# 第4课数据类型、ASCII码与分支结构
## 教学目标
- 掌握五种常用数据类型int、char、double、bool、string
- 理解 char 的 ASCII 码本质
- 能用 ASCII 码判断和转换字符类型
- 掌握 double 的格式化输出
- 掌握输入输出、运算符、类型转换和多层分支
- 认识 switch、三目运算和打擂台
## 核心考点:数据类型、类型转换与数据范围
### 考什么
选择正确的________判断表达式结果完成字符与整数转换并识别溢出或精度问题。
### 必须理解
两个整数相除会进行________除法小数部分会________。`char` 内部保存字符对应的________码。较大整数通常使用________。
### 核心写法
```cpp
double avg = ________ * sum / n;
int d = c - ________;
char c = d + ________;
long long mul = ________ * a * b;
```
### 常见错误
- 为什么 `double avg = sum / n;` 仍可能丢失小数________
- 字符 `'5'` 和整数 `5` 是否相同________________
- 怎样避免 `a * b` 在赋值前已经溢出________________
### 判断是否掌握
- [ ] 我能根据范围选择数据类型。
- [ ] 我能判断整数除法和类型转换结果。
- [ ] 我能转换数字字符与整数。
---
## 一级、二级核心:运算、转换与分支
### 类型转换
```cpp
double avg = ______ * sum / cnt;
int num = (int)ch;
char ch = (char)num;
```
整数除法会不会保留小数______。
### 多层分支
```cpp
if (score >= 90) {
cout << "A";
} else if (score >= 80) {
cout << "B";
} else {
cout << "C";
}
```
### `switch` 语句
```cpp
switch (op) {
case 1: cout << "加法"; ______;
case 2: cout << "减法"; ______;
default: cout << "无效选择";
}
```
### 三目运算
```cpp
int ma = (a > b) ? ______ : ______;
```
### 二次打擂台
先比较 ______,相同时再比较 ______。擂主应使用第一个合法候选值初始化。
## 课堂编程题
### 数据类型部分
#### 课堂1超大长方体体积【适中】
**题目描述:** 输入长方体的长、宽、高,输出体积。三个量都能用 `int` 保存,但乘积可能超过 `int` 范围。
**输入格式:** 输入三个正整数,依次表示长、宽和高。
**输出格式:** 长方体体积。
**数据范围:** `1≤a,b,c≤10^6`,答案不超过 `10^18`
**输入样例:**
```text
100000 100000 2
```
**输出样例:**
```text
20000000000
```
**解题思路:**`1LL*a*b*c` 使第一次乘法就按 `long long` 计算。
**易错提醒:** 只把结果变量写成 `long long` 不够,右侧的乘法也必须提前转换。
#### 课堂2比赛平均分【适中】
**题目描述:** 输入三名选手的总分和比赛场数,分别输出三人的平均分,保留两位小数;最后输出最高平均分。
**输入格式:** 依次输入三名选手的总分和比赛场数。
**输出格式:** 第一行三个平均分;第二行最高平均分,均保留两位小数。
**数据范围:** `1≤n≤100``0≤s1,s2,s3≤10000`
**输入样例:**
```text
25 20 29 3
```
**输出样例:**
```text
8.33 6.67 9.67
9.67
```
**解题思路:**`1.0*s/n` 触发浮点除法,使用 `fixed``setprecision(2)`
**易错提醒:** `double a=s1/n` 仍会先做整数除法,小数已经丢失。
#### 课堂3字母变身与平移【适中】
**题目描述:** 输入一个英文字母和整数 `k`。先把字母转换为相反大小写,再在保持新大小写的前提下向后循环平移 `k` 位。
**输入格式:** 输入一个英文字母和向后平移的位数。
**输出格式:** 转换并平移后的字母。
**数据范围:** `0≤k≤10^9`
**输入样例:**
```text
Y 3
```
**输出样例:**
```text
b
```
**解题思路:** 大小写互转可利用 ASCII 差值;平移只需使用 `k%26`,公式为 `(c-base+k)%26+base`
**易错提醒:** 平移必须循环;不能直接输出 `c+k`,否则可能越过字母范围。
#### 课堂4账号检查器【适中】
**题目描述:** 输入一个不含空格的字符串。若长度不少于 6且同时含有大写字母、小写字母和数字输出 `YES`,否则输出 `NO`
**输入格式:** 一个字符串。
**输出格式:** `YES``NO`
**数据范围:** 字符串长度不超过 100。
**输入样例:**
```text
Abc123
```
**输出样例:**
```text
YES
```
**解题思路:** 用三个 `bool` 变量记录是否出现三类字符,遍历 `string` 后综合判断。
**易错提醒:** `bool` 只记录真假;三个条件必须同时满足,应使用 `&&`
### 分支结构部分
#### 课堂5阶梯电费【适中】
**题目描述:** 某月用电量不超过 180 度的部分每度 0.50 元181280 度的部分每度 0.60 元;超过 280 度的部分每度 0.80 元。若总电费超过 200 元再加收 5% 服务费。输出应付金额。
**输入格式:** 输入一个非负整数,表示本月用电量。
**输出格式:** 应付金额,保留两位小数。
**数据范围:** `0≤n≤10000`
**输入样例:**
```text
300
```
**输出样例:**
```text
166.00
```
**解题思路:** 用多层 `if-else if-else` 分段累计;计算完成后再独立判断是否加收服务费。
**易错提醒:** 阶梯收费是分段累计,不是把全部用电量直接乘最高档单价。
#### 课堂6游乐园购票方案【适中】
**题目描述:** 输入年龄、身高和是否有成人陪同。身高低于 120 厘米免费;否则年龄不满 12 岁时,有成人陪同票价 30 元无陪同不得入园1259 岁票价 80 元60 岁及以上票价 40 元。周末所有非免费票加收 10 元。
**输入格式:** 依次输入年龄、身高、是否有成人陪同和是否为周末;后两项用 0 或 1 表示。
**输出格式:** 免费输出 `FREE`,不得入园输出 `DENIED`,否则输出票价。
**数据范围:** `1≤age≤120``50≤height≤220`
**输入样例:**
```text
10 135 1 1
```
**输出样例:**
```text
40
```
**解题思路:** 先判断身高;不免费时再按年龄嵌套判断,儿童分支中继续判断陪同;最后对合法票价判断周末加价。
**易错提醒:** "不得入园"不是票价 0输出后应立即结束避免继续加价。
#### 课堂7最佳选手【适中】
**题目描述:** 输入 `n` 名选手的分数,输出最高分和第一次取得该分数的选手编号。
**输入格式:** 第一行输入选手人数;第二行输入每名选手的分数。
**输出格式:** 最高分和选手编号(从 1 开始)。
**数据范围:** `1≤n≤100`,分数为 `0100`
**输入样例:**
```text
5
88 95 90 95 70
```
**输出样例:**
```text
95 2
```
**解题思路:** 用第一名选手初始化擂主;只有出现严格更高分时才更新,便可保留最早编号。
**易错提醒:** 擂主必须用第一个合法数据初始化;相等时不更新才能保留第一次出现的位置。
#### 课堂8最接近目标的候选人【适中】
**题目描述:** 输入目标分 `target` 以及 `n` 名候选人的编号和分数。选择与目标分距离最小者;距离相同时选分数较高者;仍相同则选编号较小者。
**输入格式:** 第一行输入候选人数和目标分;随后每行输入一名候选人的编号和分数。
**输出格式:** 入选者编号和分数。
**数据范围:** `1≤n≤100`,编号互不相同,分数为 `0100`
**输入样例:**
```text
4 80
3 76
8 84
2 84
5 90
```
**输出样例:**
```text
2 84
```
**解题思路:** 依次比较距离、分数、编号,三层优先级构成完整更新条件。
**易错提醒:** 三层规则按先后顺序连接,每个次级规则都只能在前级相等时生效。
## 课后作业
> 请完成本课对应的课后作业,见编程题文件。

View File

@@ -0,0 +1,283 @@
# 第4课数据类型、ASCII码与分支结构
## 教学目标
- 掌握五种常用数据类型int、char、double、bool、string
- 理解 char 的 ASCII 码本质
- 能用 ASCII 码判断和转换字符类型
- 掌握 double 的格式化输出
- 掌握输入输出、算术/关系/逻辑运算与类型转换
- 掌握 if、else if、else、switch 和三目运算
- 掌握普通打擂台与带并列规则的二次打擂台
## 核心考点:数据类型、类型转换与数据范围
### 考什么
考试会要求判断变量应使用哪种类型、计算表达式结果、完成字符与整数转换,或分析整数溢出和小数精度。真正的重点是:数据以什么形式存储,会直接影响运算结果。
### 必须理解
`int` 存整数,范围有限;较大的整数应使用 `long long``double` 存小数,但可能存在微小精度误差。`char` 表面是字符,内部保存对应的 ASCII 整数,因此 `'7' - '0'` 得到整数 7。两个整数相除仍做整数除法小数部分会被截掉只要一边先转成 `double`,才会保留小数。赋值或运算时发生类型转换,可能扩大范围,也可能丢失信息。
### 核心写法
```cpp
double average = 1.0 * sum / n; // 先变成浮点运算
int digit = c - '0'; // 数字字符转整数
char c = digit + '0'; // 0~9 转数字字符
long long product = 1LL * a * b;
```
`1.0` 触发浮点运算,`1LL` 让乘法从一开始就在 `long long` 范围内进行。
### 常见错误
- `double average = sum / n;` 中右边先做整数除法,赋给 double 时小数已经丢失。
- 把字符 `'5'` 当成整数 5它们的存储值不同。
- 结果变量使用 `long long`,但 `a * b` 已先按 int 溢出。
-`==` 直接比较复杂浮点计算结果,忽略精度误差。
### 判断是否掌握
能根据数据范围选择 `int``long long``double`,能手算整数除法与类型转换结果,并能完成数字字符和整数之间的转换。
---
## 一级、二级核心:运算、转换与分支
### 输入输出与运算符
一级要求熟练使用 `cin``cout`,掌握算术、关系和逻辑运算:
```cpp
int a, b;
cin >> a >> b;
cout << a + b << endl;
bool valid = (a >= 0 && a <= 100);
```
整数除法会丢弃小数部分;需要小数时,应让至少一个运算数先转成 `double`
### 隐式转换与强制转换
```cpp
double average = 1.0 * sum / count; // 隐式转换为 double
int code = (int)letter; // 强制转换为 int
char letter = (char)code; // 强制转换为 char
```
类型范围变大通常较安全;从浮点数转成整数会丢弃小数部分。
### 多层分支
```cpp
if (score >= 90) {
cout << "A";
} else if (score >= 80) {
cout << "B";
} else if (score >= 60) {
cout << "C";
} else {
cout << "D";
}
```
多个条件互斥时使用 `else if`,避免多个独立 `if` 重复执行。
### `switch` 语句
```cpp
switch (choice) {
case 1: cout << "加法"; break;
case 2: cout << "减法"; break;
default: cout << "无效选择";
}
```
`switch` 适合根据一个整数或字符的不同取值进行分支。每个 `case` 后通常需要 `break`,否则会继续执行后面的分支。
### 三目运算
```cpp
int bigger = (a > b) ? a : b;
```
三目运算适合简单的二选一,不适合代替复杂的多层分支。
### 打擂台与二次规则
寻找最接近目标值的数时,先比较距离;距离相同再比较数值:
```cpp
if (abs(x - target) < abs(best - target) ||
(abs(x - target) == abs(best - target) && x < best)) {
best = x;
}
```
擂主必须用第一个合法候选值初始化,不能随意设为 0。
## 题目一判断字符类型并输出ASCII码
### 核心代码(一句话)
> 字符类型判断的核心:**char 本质是整数ASCII码可以在字符范围和整数之间自由比较和转换。**
### 自然语言思路
键盘上敲的每个字符,在计算机里都存成了一个整数——这就是 **ASCII 码**
记住三个关键 ASCII 码:
- `'0'` = 48`'1'`~`'9'` 依次递增
- `'A'` = 65`'B'`~`'Z'` 依次递增
- `'a'` = 97`'b'`~`'z'` 依次递增
所以判断一个字符是什么类型,有两种方法:
**方法一(看 ASCII 码范围)**
- 如果 `c``'A'` ~ `'Z'` 之间(即 65~90→ 大写字母
- 如果 `c``'a'` ~ `'z'` 之间(即 97~122→ 小写字母
- 如果 `c``'0'` ~ `'9'` 之间(即 48~57→ 数字
- 否则 → 其他字符
**方法二用第1课学的自带函数**
- `isupper(c)` → 大写字母
- `islower(c)` → 小写字母
- `isdigit(c)` → 数字
最后,`int(c)``(int)c` 直接输出字符对应的 ASCII 码值。
### 伪代码
```
输入字符 ch
如果 ch 在 'A' ~ 'Z' 之间:
输出 "大写字母"
否则如果 ch 在 'a' ~ 'z' 之间:
输出 "小写字母"
否则如果 ch 在 '0' ~ '9' 之间:
输出 "数字"
否则:
输出 "其他字符"
输出 "ASCII码 = " + int(ch)
```
### 真实代码
```cpp
#include <iostream>
#include <cctype> // isupper、islower、isdigit
using namespace std;
int main() {
char ch;
cin >> ch;
// 方法一ASCII 码范围判断
if (ch >= 'A' && ch <= 'Z') {
cout << "大写字母" << endl;
} else if (ch >= 'a' && ch <= 'z') {
cout << "小写字母" << endl;
} else if (ch >= '0' && ch <= '9') {
cout << "数字" << endl;
} else {
cout << "其他字符" << endl;
}
// 输出 ASCII 码值
cout << "ASCII码 = " << (int)ch << endl;
return 0;
}
```
### 方法二(用自带函数,更简洁)
```cpp
if (isupper(ch)) {
cout << "大写字母" << endl;
} else if (islower(ch)) {
cout << "小写字母" << endl;
} else if (isdigit(ch)) {
cout << "数字" << endl;
} else {
cout << "其他字符" << endl;
}
```
### 注意点
1. **`char` 能直接比大小**`ch >= 'A'` 就是在比较 ASCII 码值,不需要先转成 int。
2. **字符写单引号**`'A'` 不是 `"A"`,单引号是 char双引号是字符串。
3. **ASCII 码值记忆技巧**`'0'`=48`'A'`=65`'a'`=97。小写字母 = 大写字母 + 32。
4. **大小写互转**:大写转小写 `ch + 32`,小写转大写 `ch - 32`。或者用 `tolower(ch)` / `toupper(ch)`(返回 int需要转回 char
5. **不要混用 string 和 char**`string` 是字符串(双引号),`char` 是单个字符(单引号),两者不同。
---
## 题目二:(待橙子补充)
> 建议覆盖double 格式化输出、int vs long long 范围等
---
### 核心代码(一句话)
>
### 自然语言思路
### 伪代码
### 真实代码
```cpp
```
### 注意点
---
## 题目三:(待橙子补充)
> 建议覆盖bool 类型、string 基本操作等
---
### 核心代码(一句话)
>
### 自然语言思路
### 伪代码
### 真实代码
```cpp
```
### 注意点
---
## 本课打油诗
> int 存整数范围广char 单字符本质藏。
> double 小数保精度bool 对错分两旁。
> string 处理字符串,五种类型记心上。
---
## ASCII 码速查表(常用)
| 字符 | ASCII 码 |
|------|----------|
| `'0'` ~ `'9'` | 48 ~ 57 |
| `'A'` ~ `'Z'` | 65 ~ 90 |
| `'a'` ~ `'z'` | 97 ~ 122 |
| 空格 `' '` | 32 |
> 记忆口诀:**4A = 65**,不是"4个A",是"数字4开头A是65"。小写比大写多32。
---
## 课后作业
(待补充)
1.
2.

View File

@@ -0,0 +1,581 @@
# 第3课编程题教师版
## 本课知识点覆盖表
| 核心知识点 | GESP等级 | 对应课堂题 |
|---|---|---|
| `int``long long`、数据范围与 `1LL` | 一级必考 | 课堂1 |
| `double`、整数除法、类型转换、格式化输出 | 一级必考 | 课堂2 |
| `char`、ASCII 码、大小写转换、字母平移 | 一级/二级必考 | 课堂3 |
| `bool``string`、字符与数字转换 | 一级/二级 | 课堂4 |
> 本课加强大小写转换和字母平移训练。字母平移均按英文字母表循环,即 `Z` 的下一个字母是 `A`。
## 一、课堂练习4题
### 课堂1超大长方体体积【适中】
**题目描述:** 输入长方体的长、宽、高,输出体积。三个量都能用 `int` 保存,但乘积可能超过 `int` 范围。
**输入格式:** 输入三个正整数,依次表示长、宽和高。
**输出格式:** 长方体体积。
**数据范围:** `1≤a,b,c≤10^6`,答案不超过 `10^18`
**输入样例:**
```text
100000 100000 2
```
**输出样例:**
```text
20000000000
```
**解题思路:**`1LL*a*b*c` 使第一次乘法就按 `long long` 计算。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int a, b, c;
cin >> a >> b >> c;
long long volume = 1LL * a * b * c;
cout << volume;
return 0;
}
```
**易错提醒:** 只把结果变量写成 `long long` 不够,右侧的乘法也必须提前转换。
### 课堂2比赛平均分【适中】
**题目描述:** 输入三名选手的总分和比赛场数,分别输出三人的平均分,保留两位小数;最后输出最高平均分。
**输入格式:** 依次输入三名选手的总分和比赛场数。
**输出格式:** 第一行三个平均分;第二行最高平均分,均保留两位小数。
**数据范围:** `1≤n≤100``0≤s1,s2,s3≤10000`
**输入样例:**
```text
25 20 29 3
```
**输出样例:**
```text
8.33 6.67 9.67
9.67
```
**解题思路:**`1.0*s/n` 触发浮点除法,使用 `fixed``setprecision(2)`
**参考代码:**
```cpp
#include <iostream>
#include <iomanip>
#include <algorithm>
using namespace std;
int main() {
int s1, s2, s3, n;
cin >> s1 >> s2 >> s3 >> n;
double a = 1.0 * s1 / n;
double b = 1.0 * s2 / n;
double c = 1.0 * s3 / n;
cout << fixed << setprecision(2);
cout << a << " " << b << " " << c << "\n";
cout << max(a, max(b, c));
return 0;
}
```
**易错提醒:** `double a=s1/n` 仍会先做整数除法,小数已经丢失。
### 课堂3字母变身与平移【适中】
**题目描述:** 输入一个英文字母和整数 `k`。先把字母转换为相反大小写,再在保持新大小写的前提下向后循环平移 `k` 位。
**输入格式:** 输入一个英文字母和向后平移的位数。
**输出格式:** 转换并平移后的字母。
**数据范围:** `0≤k≤10^9`
**输入样例:**
```text
Y 3
```
**输出样例:**
```text
b
```
**解题思路:** 大小写互转可利用 ASCII 差值;平移只需使用 `k%26`,公式为 `(c-base+k)%26+base`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
char c;
long long k;
cin >> c >> k;
if (c >= 'A' && c <= 'Z') c = c + ('a' - 'A');
else c = c - ('a' - 'A');
char base = (c >= 'a' && c <= 'z') ? 'a' : 'A';
c = (c - base + k % 26) % 26 + base;
cout << c;
return 0;
}
```
**易错提醒:** 平移必须循环;不能直接输出 `c+k`,否则可能越过字母范围。
### 课堂4账号检查器【适中】
**题目描述:** 输入一个不含空格的字符串。若长度不少于 6且同时含有大写字母、小写字母和数字输出 `YES`,否则输出 `NO`
**输入格式:** 一个字符串。
**输出格式:** `YES``NO`
**数据范围:** 字符串长度不超过 100。
**输入样例:**
```text
Abc123
```
**输出样例:**
```text
YES
```
**解题思路:** 用三个 `bool` 变量记录是否出现三类字符,遍历 `string` 后综合判断。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s;
bool upper = false, lower = false, digit = false;
for (char c : s) {
if (c >= 'A' && c <= 'Z') upper = true;
else if (c >= 'a' && c <= 'z') lower = true;
else if (c >= '0' && c <= '9') digit = true;
}
bool valid = s.size() >= 6 && upper && lower && digit;
cout << (valid ? "YES" : "NO");
return 0;
}
```
**易错提醒:** `bool` 只记录真假;三个条件必须同时满足,应使用 `&&`
## 二、课后作业3题
### 课后1小写转大写【简单】
**题目描述:** 输入一个小写字母,使用 ASCII 码运算转成大写字母。
**输入格式:** 一个小写字母。
**输出格式:** 对应的大写字母。
**数据范围:** `az`
**输入样例:**
```text
g
```
**输出样例:**
```text
G
```
**解题思路:** 小写字母比对应大写字母的 ASCII 码大 32计算 `c-'a'+'A'`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
char c;
cin >> c;
cout << (char)(c - 'a' + 'A');
return 0;
}
```
**易错提醒:** 字符常量必须使用单引号。
### 课后2数字字符还原【简单】
**题目描述:** 输入一个数字字符,输出它代表的整数再加 5 的结果。
**输入格式:** 输入一个范围为 09 的数字字符。
**输出格式:** 一个整数。
**数据范围:** `'0'≤c≤'9'`
**输入样例:**
```text
7
```
**输出样例:**
```text
12
```
**解题思路:** 使用 `c-'0'` 把数字字符转换成整数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
char c;
cin >> c;
int number = c - '0';
cout << number + 5;
return 0;
}
```
**易错提醒:** 直接计算 `c+5` 得到的是新的字符编码,不是题目需要的整数。
### 课后3整数平均数【简单】
**题目描述:** 输入两个整数,输出它们的平均数,保留一位小数。
**输入格式:** 输入两个整数。
**输出格式:** 平均数,保留一位小数。
**数据范围:** 两数绝对值不超过 `10^6`
**输入样例:**
```text
2 5
```
**输出样例:**
```text
3.5
```
**解题思路:**`(a+b)/2.0` 进行浮点除法。
**参考代码:**
```cpp
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
int a, b;
cin >> a >> b;
cout << fixed << setprecision(1) << (a + b) / 2.0;
return 0;
}
```
**易错提醒:** 除数写成 `2` 会进行整数除法,写成 `2.0` 才保留小数。
## 三、扩展练习7题
### 扩展1向后移动一位【基础综合】
**题目描述:** 输入一个小写字母,输出字母表中的下一个小写字母;`z` 的下一个字母为 `a`
**输入格式:** 一个小写字母。
**输出格式:** 平移后的字母。
**数据范围:** `az`
**输入样例:**
```text
z
```
**输出样例:**
```text
a
```
**解题思路:**`(c-'a'+1)%26+'a'` 实现循环。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
char c;
cin >> c;
cout << (char)((c - 'a' + 1) % 26 + 'a');
return 0;
}
```
**易错提醒:** 单独写 `c+1` 无法处理 `z`
### 扩展2双向字母平移【基础综合】
**题目描述:** 输入一个大写字母、方向 `L``R` 和步数 `k`。向左或向右循环平移,输出结果。
**输入格式:** 依次输入一个大写字母、平移方向和移动位数。
**输出格式:** 平移后的大写字母。
**数据范围:** `0≤k≤10^9`
**输入样例:**
```text
B L 3
```
**输出样例:**
```text
Y
```
**解题思路:** 右移加 `k`;左移等价于加 `26-k%26`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
char c, direction;
long long k;
cin >> c >> direction >> k;
k %= 26;
if (direction == 'L') k = (26 - k) % 26;
cout << (char)((c - 'A' + k) % 26 + 'A');
return 0;
}
```
**易错提醒:** C++ 中负数取模可能仍为负数,所以左移时先转成非负偏移量。
### 扩展3两个字母的距离【中等】
**题目描述:** 输入两个同为大写的字母,输出从第一个字母向后循环移动到第二个字母所需的最少步数。
**输入格式:** 输入两个大写字母。
**输出格式:** 向后平移步数。
**数据范围:** `AZ`
**输入样例:**
```text
X C
```
**输出样例:**
```text
5
```
**解题思路:** 计算 `(b-a+26)%26`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
char a, b;
cin >> a >> b;
cout << (b - a + 26) % 26;
return 0;
}
```
**易错提醒:** 加 26 是为了避免结果为负数。
### 扩展4字母镜像【中等】
**题目描述:** 字母表两端互为镜像:`A↔Z``B↔Y`,小写字母同理。输入一个字母,输出它的镜像字母,并保持大小写。
**输入格式:** 一个英文字母。
**输出格式:** 镜像字母。
**数据范围:** 英文字母。
**输入样例:**
```text
C
```
**输出样例:**
```text
X
```
**解题思路:** 大写字母镜像为 `'A'+'Z'-c`,小写同理。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
char c;
cin >> c;
if (c >= 'A' && c <= 'Z') cout << (char)('A' + 'Z' - c);
else cout << (char)('a' + 'z' - c);
return 0;
}
```
**易错提醒:** 大写和小写必须使用各自的范围计算。
### 扩展5凯撒口令【中等】
**题目描述:** 输入一个不含空格的字符串和非负整数 `k`。所有英文字母向后循环平移 `k` 位,保持大小写;数字和符号不变。
**输入格式:** 第一行输入一个不含空格的字符串;第二行输入向后平移的位数。
**输出格式:** 平移后的字符串。
**数据范围:** 字符串长度不超过 100`0≤k≤10^9`
**输入样例:**
```text
Az-9
2
```
**输出样例:**
```text
Cb-9
```
**解题思路:** 根据字符范围选择基准 `'A'``'a'`;其他字符原样保留。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
long long k;
cin >> s >> k;
k %= 26;
for (char &c : s) {
if (c >= 'A' && c <= 'Z')
c = (c - 'A' + k) % 26 + 'A';
else if (c >= 'a' && c <= 'z')
c = (c - 'a' + k) % 26 + 'a';
}
cout << s;
return 0;
}
```
**易错提醒:** 平移后必须写回字符串;数字和符号不能参与字母公式。
### 扩展6最短平移方向【略难】
**题目描述:** 输入两个大写字母。求从第一个字母循环平移到第二个字母的最少步数;若向左和向右步数相同,输出 `BOTH`,否则输出 `LEFT``RIGHT`,并输出步数。
**输入格式:** 输入两个大写字母。
**输出格式:** 方向和最少步数。
**数据范围:** `AZ`
**输入样例:**
```text
A X
```
**输出样例:**
```text
LEFT 3
```
**解题思路:** 右移步数为 `(b-a+26)%26`,左移步数为 `(a-b+26)%26`,比较两者。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
char a, b;
cin >> a >> b;
int right = (b - a + 26) % 26;
int left = (a - b + 26) % 26;
if (left < right) cout << "LEFT " << left;
else if (right < left) cout << "RIGHT " << right;
else cout << "BOTH " << left;
return 0;
}
```
**易错提醒:** 两个字母相同也属于左右步数相同,此时输出 `BOTH 0`
### 扩展7解密未知平移量【略难】
**题目描述:** 某个大写字母经过向后循环平移变成另一个大写字母。输入原字母和结果字母,求 `025` 范围内唯一的平移量 `k`;再输入第三个大写字母,按同样规则平移并输出。
**输入格式:** 依次输入原字母、加密后的字母和需要继续加密的字母。
**输出格式:** 第一行平移量 `k`;第二行第三个字母平移后的结果。
**数据范围:** `AZ`
**输入样例:**
```text
Y B Z
```
**输出样例:**
```text
3
C
```
**解题思路:** 先由前两个字母计算 `k=(encoded-original+26)%26`,再把 `k` 应用于第三个字母。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
char original, encoded, target;
cin >> original >> encoded >> target;
int k = (encoded - original + 26) % 26;
char answer = (target - 'A' + k) % 26 + 'A';
cout << k << "\n" << answer;
return 0;
}
```
**易错提醒:** 计算平移量和应用平移量使用的是同一个循环公式。

View File

@@ -0,0 +1,151 @@
# 第5课嵌套循环与图形打印
## 教学目标
- 理解嵌套循环的执行流程(外层管行、内层管列)
- 能分析图形规律,写出对应的行列公式
- 掌握矩形、直角三角形、等腰三角形、菱形的打印方法
- 掌握多层循环以及分支与循环相互嵌套
## 核心考点:嵌套循环中的行列关系
### 考什么
根据图形推导每一行的________数和________数再使用嵌套循环输出。
### 必须理解
________循环负责行数________循环负责当前行中的字符数量。等腰三角形第 i 行有________个前导空格、________个星号。
### 核心写法
```cpp
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= ________; j++) cout << ' ';
for (int j = 1; j <= ________; j++) cout << '*';
____________________;
}
```
### 常见错误
- 换行应写在内层循环里面还是外面________________
- 为什么内层循环的次数常常与 i 有关________________
- 行号从0开始时能否直接使用从1开始的公式________
### 判断是否掌握
- [ ] 我能列出每行空格数和符号数。
- [ ] 我能由行号推导数量公式。
- [ ] 我能正确安排内外层循环与换行。
---
## 课堂编程题
### 课堂1实心与空心矩形【适中】
**题目描述:** 输入行数 `n`、列数 `m` 和模式 `type`。当 `type=1` 时打印实心矩形;当 `type=2` 时打印空心矩形。
**输入格式:** 依次输入行数、列数和图形模式。
**输出格式:** 用星号打印指定矩形。
**数据范围:** `1≤n,m≤30``type` 为 1 或 2。
**输入样例:**
```text
3 5 2
```
**输出样例:**
```text
*****
* *
*****
```
**解题思路:** 双层循环枚举行列。实心模式总输出星号;空心模式只在首尾行或首尾列输出星号。
**易错提醒:** 每打印完一行才换行;`n=1``m=1` 时所有位置都在边界上。
### 课堂2四种直角三角形【适中】
**题目描述:** 输入 `n` 和类型 `type`,打印边长为 `n` 的直角三角形1 左下、2 右下、3 左上、4 右上。
**输入格式:** 输入三角形边长和图形类型。
**输出格式:** 指定方向的星号三角形。
**数据范围:** `1≤n≤30``1≤type≤4`
**输入样例:**
```text
4 2
```
**输出样例:**
```text
*
**
***
****
```
**解题思路:**`i` 行星号数量为 `i``n-i+1`;右对齐时先输出相应数量空格。
**易错提醒:** 右对齐不是改变星号数量,而是在星号前补空格。
### 课堂3等腰三角形与菱形【适中】
**题目描述:** 输入 `n` 和模式 `type`。模式 1 打印高为 `n` 的实心等腰三角形;模式 2 打印上下高度均为 `n`、中间行不重复的菱形。
**输入格式:** 输入图形高度和图形模式。
**输出格式:** 指定图形。
**数据范围:** `1≤n≤20`
**输入样例:**
```text
3 2
```
**输出样例:**
```text
*
***
*****
***
*
```
**解题思路:** 上半部分第 `i` 行有 `n-i` 个空格和 `2i-1` 个星号;菱形下半部分按相反规律输出。
**易错提醒:** 菱形下半部分从 `n-1` 开始,避免中间最长行重复。
### 课堂4九九乘法表【适中】
**题目描述:** 输入 `n`,打印 `1n` 的下三角乘法表。第 `i` 行输出 `1×i``i×i`
**输入格式:** 输入乘法表的最大因数。
**输出格式:** 下三角乘法表,每项格式为 `j*i=积`,项之间一个空格。
**数据范围:** `1≤n≤9`
**输入样例:**
```text
3
```
**输出样例:**
```text
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
```
**解题思路:** 外层 `i` 控制行和第二个因数,内层 `j` 从 1 走到 `i`
**易错提醒:** 内层上界是当前行号 `i`,不是固定的 `n`
---
## 课后作业
> 请完成本课对应的课后作业,见编程题文件。

View File

@@ -0,0 +1,310 @@
# 第5课嵌套循环与图形打印
## 教学目标
- 理解嵌套循环的执行流程(外层管行、内层管列)
- 能分析图形规律,写出对应的行列公式
- 掌握矩形、直角三角形、等腰三角形、菱形的打印方法
- 掌握多层循环以及分支与循环相互嵌套的二级必考写法
## 核心考点:嵌套循环中的行列关系
### 考什么
题目会给出矩形、三角形或菱形,要求分析每一行应输出多少空格和符号,再用嵌套循环打印。考试重点不是背某个图形,而是能从行号推导本行各部分的数量。
### 必须理解
外层循环每执行一次就完成一整行,因此负责行数;内层循环在当前行中重复输出,负责列数或某种字符数量。解决图形题应先列出“第 i 行有多少空格、多少星号”,再把数量写成关于 i 和总行数 n 的公式。例如等腰三角形第 i 行:前导空格 `n-i` 个,星号 `2*i-1` 个。每一类字符分别使用一个内层循环,行末统一换行。
### 核心写法
```cpp
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n - i; j++) cout << ' ';
for (int j = 1; j <= 2 * i - 1; j++) cout << '*';
cout << endl;
}
```
外层的 i 表示当前行,两个内层循环的上界由 i 决定,所以每行形状都会变化。
### 常见错误
- 把换行写进内层循环,导致每输出一个字符就换行。
- 内层循环上界始终写 n无法随行号改变图形。
- 行号从 0 开始,却直接套用从 1 开始推导的公式。
- 只凭眼睛试代码,不先写出行号与数量表。
### 判断是否掌握
面对新图形时,能先列前三行的空格数和符号数,推导出关于 i 的公式,再独立写出外层、内层和换行代码。
---
## 题目一:打印实心矩形
### 核心代码(一句话)
> 嵌套循环的核心:**外层循环管行数,内层循环管列数**,两层 `for` 一套就行。
### 自然语言思路
我们要打印一个 n 行 m 列的矩形,全是星号。
第一步确定要印几行n 行,所以外层循环从 1 到 n。
第二步每一行里面要印几个星号m 个,所以内层循环从 1 到 m。
第三步,每印完一行,记得换行 `cout << endl`
第四步,把上面三步写成代码 —— 外层 `for` 套一个内层 `for`,里面 `cout` 星号,外面 `cout` 换行。
> 想象你在一行一行地写字:外层决定写几行,内层决定每行写几个字。
### 伪代码
```
1. 输入 n, m
2. for i = 1 到 n // 外层:控制行
3. for j = 1 到 m // 内层:控制列
4. 输出 "* "
5. 输出换行
```
### 真实代码
```cpp
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) { // 外层循环n 行
for (int j = 1; j <= m; j++) { // 内层循环:每行 m 个
cout << "* ";
}
cout << endl; // 每行结束换行
}
return 0;
}
```
### 注意点
1. **内层循环变量名**:习惯用 `j`,因为 `i` 已经被外层用了。不要在外层和内层用同一个变量名。
2. **换行位置**`cout << endl` 放在外层循环里面、内层循环外面,每行结束只换一次。
3. **嵌套理解口诀**:外层每走一步,内层跑完全程。就像时钟——时针走一格,分针转一整圈。
---
## 题目二:直角三角形(左下 + 右下)
### 核心代码(一句话)
> 直角三角形的核心:**第 i 行打印 i 个星号**,内层循环的终止条件用 `j <= i`。
### 自然语言思路
**左下直角三角形**
第一步观察规律第1行1个星第2行2个星第3行3个星……第 i 行 i 个星。
第二步,每行的星号数量**等于行号**。
第三步,内层循环写成 `for (j = 1; j <= i; j++)`,就搞定了。
**右下直角三角形**
第一步先看空格第1行前面有 n-1 个空格第2行前面有 n-2 个空格……第 i 行前面有 n-i 个空格。
第二步,空格用**另一个内层循环**先打印:`for (j = 1; j <= n - i; j++)`
第三步,再打印星号,和第 i 行 i 个星一样。
第四步,每行顺序:先空格、后星号、再换行。
### 伪代码
**左下直角三角形**
```
1. 输入 n
2. for i = 1 到 n
3. for j = 1 到 i // 第 i 行 i 个星
4. 输出 "* "
5. 输出换行
```
**右下直角三角形**
```
1. 输入 n
2. for i = 1 到 n
3. for j = 1 到 n-i // 先打 n-i 个空格
4. 输出 " " // 两个空格(跟星号对齐)
5. for j = 1 到 i // 再打 i 个星
6. 输出 "* "
7. 输出换行
```
### 真实代码
**左下直角三角形**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) { // 关键j <= i
cout << "* ";
}
cout << endl;
}
return 0;
}
```
**右下直角三角形**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
// 先打空格
for (int j = 1; j <= n - i; j++) {
cout << " "; // 两个空格,跟 "* " 宽度一致
}
// 再打星号
for (int j = 1; j <= i; j++) {
cout << "* ";
}
cout << endl;
}
return 0;
}
```
### 注意点
1. **空格数量要对齐**:如果星号之间用 `"* "`(星号+空格),那么空格也要打印两个空格 `" "`,否则对不齐。
2. **左下直角三角形最简单**:第 i 行 = i 个星,这是所有图形打印的母题,先把这个吃透。
3. **右下 = 左下 + 空格**:右下直角就是把左下直角往右推,推的力度 = 前面的空格数。
---
## 题目三:等腰三角形与菱形
### 核心代码(一句话)
> 等腰三角形的核心:第 i 行有 **2i - 1** 个星号,前面的空格数为 **n - i**;菱形 = 正等腰 + 倒等腰。
### 自然语言思路
**等腰三角形**
第一步观察规律n=5 时第1行1个星第2行3个星第3行5个星……每行多2个星。
第二步,归纳公式:第 i 行星号数 = `2 * i - 1`
第三步,空格规律和右下直角一样:第 i 行前面 n-i 个空格。
第四步,先打空格,再打星号,换行,搞定。
**菱形**
第一步,把菱形拆成两半:上半 = n 行等腰三角形,下半 = n-1 行倒等腰三角形。
第二步,上半直接复用等腰三角形代码。
第三步,下半倒过来:从第 n-1 行开始,递减到第 1 行。空格和星号公式不变,只是循环方向反过来。
第四步,上下拼起来就是菱形。
### 伪代码
**等腰三角形**
```
1. 输入 n
2. for i = 1 到 n
3. for j = 1 到 n-i // 空格n-i 个
4. 输出 " "
5. for j = 1 到 2*i-1 // 星号2i-1 个
6. 输出 "*"
7. 输出换行
```
**菱形**
```
1. 输入 n上半部分行数
2. // 上半:正等腰三角形
3. for i = 1 到 n
4. 打空格、打星号、换行(同上)
5. // 下半:倒等腰三角形
6. for i = n-1 递减到 1
7. 打空格、打星号、换行(同上)
```
### 真实代码
**等腰三角形**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
// 空格
for (int j = 1; j <= n - i; j++) {
cout << " ";
}
// 星号2i - 1
for (int j = 1; j <= 2 * i - 1; j++) {
cout << "*";
}
cout << endl;
}
return 0;
}
```
**菱形**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
// 上半部分:正等腰三角形
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n - i; j++) cout << " ";
for (int j = 1; j <= 2 * i - 1; j++) cout << "*";
cout << endl;
}
// 下半部分倒等腰三角形n-1 行)
for (int i = n - 1; i >= 1; i--) {
for (int j = 1; j <= n - i; j++) cout << " ";
for (int j = 1; j <= 2 * i - 1; j++) cout << "*";
cout << endl;
}
return 0;
}
```
### 注意点
1. **等腰三角形星号公式 `2*i-1`**这是最核心的公式建议学生背下来。推导第1行1个每行多2个 → 等差数列 → 1 + 2×(i-1) = 2i-1。
2. **菱形下半部分循环递减**`for (i = n-1; i >= 1; i--)`,注意从 `n-1` 开始,不是 `n`,否则中间那行会重复。
3. **空格只用一个空格**:等腰三角形通常用一个空格 `" "` 而不是两个,因为星号之间不加空格。
4. **如果 n=1**:下半部分 `i = 0``0 >= 1` 为假,不执行,只打印一行星号,也是对的。
---
## 图形打印技巧总结
| 图形 | 第 i 行星号数 | 第 i 行空格数 | 关键点 |
|------|-------------|-------------|--------|
| 左下直角三角形 | `i` | 0 | 最简单,母题 |
| 左上直角三角形 | `n - i + 1` | 0 | 倒过来即可 |
| 右下直角三角形 | `i` | `n - i` | = 左下 + 空格 |
| 右上直角三角形 | `n - i + 1` | `i - 1` | = 左上 + 空格 |
| 等腰三角形 | `2i - 1` | `n - i` | 核心公式 |
| 倒等腰三角形 | `2i - 1` | `n - i` | i 递减即可 |
| 菱形 | 上同等腰,下同倒等腰 | 同上 | 上下拼接 |
---
## 本课打油诗
> 外层循环管行数,内层循环管列数。
> 图形打印找规律,行列关系是正路。
> 第 i 行星号看公式,空格星号分清楚。
> 等腰三角二 i 减一,菱形上下拼一處。
---
## 课后作业
1. 输入 n打印左上直角三角形第1行 n 个星第2行 n-1 个星……)
2. 输入 n打印数字三角形第1行1第2行1 2……
3. 输入 n 和 m打印 n 行 m 列的空心矩形(只有边框有星号)
4. 输入 n打印九九乘法表第 i 行输出 i 个乘法式子)

View File

@@ -0,0 +1,660 @@
# 第6课编程题教师版
## 本课知识点覆盖表
| 核心知识点 | GESP等级 | 对应课堂题 |
|---|---|---|
| 外层控制行、内层控制列、实心矩形 | 二级必考 | 课堂1 |
| 行列关系、左下与右下三角形 | 二级必考 | 课堂2 |
| 空格数与星号数、等腰三角形与菱形 | 二级必考 | 课堂3 |
| 双层循环的非图形应用、行列组合 | 二级 | 课堂4 |
## 一、课堂练习4题
### 课堂1实心与空心矩形【适中】
**题目描述:** 输入行数 `n`、列数 `m` 和模式 `type`。当 `type=1` 时打印实心矩形;当 `type=2` 时打印空心矩形。
**输入格式:** 依次输入行数、列数和图形模式。
**输出格式:** 用星号打印指定矩形。
**数据范围:** `1≤n,m≤30``type` 为 1 或 2。
**输入样例:**
```text
3 5 2
```
**输出样例:**
```text
*****
* *
*****
```
**解题思路:** 双层循环枚举行列。实心模式总输出星号;空心模式只在首尾行或首尾列输出星号。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, m, type;
cin >> n >> m >> type;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (type == 1 || i == 1 || i == n || j == 1 || j == m)
cout << "*";
else
cout << " ";
}
cout << "\n";
}
return 0;
}
```
**易错提醒:** 每打印完一行才换行;`n=1``m=1` 时所有位置都在边界上。
### 课堂2四种直角三角形【适中】
**题目描述:** 输入 `n` 和类型 `type`,打印边长为 `n` 的直角三角形1 左下、2 右下、3 左上、4 右上。
**输入格式:** 输入三角形边长和图形类型。
**输出格式:** 指定方向的星号三角形。
**数据范围:** `1≤n≤30``1≤type≤4`
**输入样例:**
```text
4 2
```
**输出样例:**
```text
*
**
***
****
```
**解题思路:**`i` 行星号数量为 `i``n-i+1`;右对齐时先输出相应数量空格。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, type;
cin >> n >> type;
for (int i = 1; i <= n; i++) {
int stars = (type == 1 || type == 2) ? i : n - i + 1;
int spaces = (type == 2 || type == 4) ? n - stars : 0;
for (int j = 1; j <= spaces; j++) cout << " ";
for (int j = 1; j <= stars; j++) cout << "*";
cout << "\n";
}
return 0;
}
```
**易错提醒:** 右对齐不是改变星号数量,而是在星号前补空格。
### 课堂3等腰三角形与菱形【适中】
**题目描述:** 输入 `n` 和模式 `type`。模式 1 打印高为 `n` 的实心等腰三角形;模式 2 打印上下高度均为 `n`、中间行不重复的菱形。
**输入格式:** 输入图形高度和图形模式。
**输出格式:** 指定图形。
**数据范围:** `1≤n≤20`
**输入样例:**
```text
3 2
```
**输出样例:**
```text
*
***
*****
***
*
```
**解题思路:** 上半部分第 `i` 行有 `n-i` 个空格和 `2i-1` 个星号;菱形下半部分按相反规律输出。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
void printRow(int spaces, int stars) {
for (int j = 0; j < spaces; j++) cout << " ";
for (int j = 0; j < stars; j++) cout << "*";
cout << "\n";
}
int main() {
int n, type;
cin >> n >> type;
for (int i = 1; i <= n; i++) printRow(n - i, 2 * i - 1);
if (type == 2) {
for (int i = n - 1; i >= 1; i--) printRow(n - i, 2 * i - 1);
}
return 0;
}
```
**易错提醒:** 菱形下半部分从 `n-1` 开始,避免中间最长行重复。
### 课堂4九九乘法表【适中】
**题目描述:** 输入 `n`,打印 `1n` 的下三角乘法表。第 `i` 行输出 `1×i``i×i`
**输入格式:** 输入乘法表的最大因数。
**输出格式:** 下三角乘法表,每项格式为 `j*i=积`,项之间一个空格。
**数据范围:** `1≤n≤9`
**输入样例:**
```text
3
```
**输出样例:**
```text
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
```
**解题思路:** 外层 `i` 控制行和第二个因数,内层 `j` 从 1 走到 `i`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
if (j > 1) cout << " ";
cout << j << "*" << i << "=" << j * i;
}
cout << "\n";
}
return 0;
}
```
**易错提醒:** 内层上界是当前行号 `i`,不是固定的 `n`
## 二、课后作业3题
### 课后1实心正方形【简单】
**题目描述:** 输入 `n`,打印 `n×n` 的星号正方形。
**输入格式:** 输入正方形的边长。
**输出格式:** 实心正方形。
**数据范围:** `1≤n≤20`
**输入样例:**
```text
3
```
**输出样例:**
```text
***
***
***
```
**解题思路:** 外层循环 `n` 行,内层每行输出 `n` 个星号。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) cout << "*";
cout << "\n";
}
return 0;
}
```
**易错提醒:** 换行语句应放在内层循环结束之后。
### 课后2左下数字三角形【简单】
**题目描述:** 输入 `n`,第 `i` 行输出 `i` 个数字 `i`
**输入格式:** 输入数字三角形的行数。
**输出格式:** 数字三角形。
**数据范围:** `1≤n≤9`
**输入样例:**
```text
4
```
**输出样例:**
```text
1
22
333
4444
```
**解题思路:** 外层行号决定输出数字,也决定内层循环次数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) cout << i;
cout << "\n";
}
return 0;
}
```
**易错提醒:** 输出的是行号 `i`,不是列号 `j`
### 课后3棋盘格【简单】
**题目描述:** 输入 `n``m`,打印棋盘格;行列编号之和为偶数输出 `#`,否则输出 `.`
**输入格式:** 输入棋盘格的行数和列数。
**输出格式:** `n``m` 列棋盘格。
**数据范围:** `1≤n,m≤30`
**输入样例:**
```text
3 4
```
**输出样例:**
```text
#.#.
.#.#
#.#.
```
**解题思路:** 双层循环枚举位置,根据 `(i+j)%2` 决定字符。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
cout << ((i + j) % 2 == 0 ? '#' : '.');
cout << "\n";
}
return 0;
}
```
**易错提醒:** 行列从 1 开始时,左上角的和为 2应输出 `#`
## 三、扩展练习7题
### 扩展1坐标表【基础综合】
**题目描述:** 输入 `n m`,按行输出所有坐标 `(i,j)`
**输入格式:** 输入坐标表的行数和列数。
**输出格式:** 每行输出一行坐标,坐标之间一个空格。
**数据范围:** `1≤n,m≤10`
**输入样例:**
```text
2 2
```
**输出样例:**
```text
(1,1) (1,2)
(2,1) (2,2)
```
**解题思路:** 外层决定第一坐标,内层决定第二坐标。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (j > 1) cout << " ";
cout << "(" << i << "," << j << ")";
}
cout << "\n";
}
return 0;
}
```
**易错提醒:** 同一行坐标之间输出空格,行末不必输出多余空格。
### 扩展2回文数字三角形【中等】
**题目描述:** 输入 `n`,第 `i` 行依次输出 `1i1`
**输入格式:** 输入回文数字三角形的行数。
**输出格式:** 回文数字三角形。
**数据范围:** `1≤n≤9`
**输入样例:**
```text
4
```
**输出样例:**
```text
1
121
12321
1234321
```
**解题思路:** 每行使用两个内层循环:第一个递增到 `i`,第二个从 `i-1` 递减到 1。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) cout << j;
for (int j = i - 1; j >= 1; j--) cout << j;
cout << "\n";
}
return 0;
}
```
**易错提醒:** 下降部分从 `i-1` 开始,避免中间数字重复。
### 扩展3空心直角三角形【中等】
**题目描述:** 输入 `n`,打印左下空心直角三角形。
**输入格式:** 输入空心直角三角形的边长。
**输出格式:** 指定图形。
**数据范围:** `2≤n≤30`
**输入样例:**
```text
5
```
**输出样例:**
```text
*
**
* *
* *
*****
```
**解题思路:**`i` 行有 `i` 个位置;首列、斜边 `j=i`、底边 `i=n` 输出星号。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
if (j == 1 || j == i || i == n) cout << "*";
else cout << " ";
}
cout << "\n";
}
return 0;
}
```
**易错提醒:** 第二行两个位置都属于边界,所以会输出两个星号。
### 扩展4字母方阵【中等】
**题目描述:** 输入 `n`,第 `i` 行第 `j` 列输出从 `A` 开始向后平移 `i+j-2` 位的字母,超过 `Z` 循环。
**输入格式:** 输入字母方阵的边长。
**输出格式:** `n×n` 字母方阵。
**数据范围:** `1≤n≤20`
**输入样例:**
```text
3
```
**输出样例:**
```text
ABC
BCD
CDE
```
**解题思路:** 用行列共同决定平移量,字符公式为 `'A'+(i+j-2)%26`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
cout << (char)('A' + (i + j - 2) % 26);
cout << "\n";
}
return 0;
}
```
**易错提醒:** ASCII 平移后要转换为字符;取模保证不会越过 `Z`
### 扩展5X形图案【中等】
**题目描述:** 输入奇数 `n`,打印 `n×n` 方阵,两条对角线输出 `*`,其他位置输出 `.`
**输入格式:** 输入一个奇数,表示方阵的边长。
**输出格式:** X 形图案。
**数据范围:** `1≤n≤31``n` 为奇数。
**输入样例:**
```text
5
```
**输出样例:**
```text
*...*
.*.*.
..*..
.*.*.
*...*
```
**解题思路:** 主对角线满足 `i=j`,副对角线满足 `i+j=n+1`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j || i + j == n + 1) cout << "*";
else cout << ".";
}
cout << "\n";
}
return 0;
}
```
**易错提醒:** 副对角线条件使用 `n+1`,因为行列编号从 1 开始。
### 扩展6空心菱形【略难】
**题目描述:** 输入 `n`,打印总高度为 `2n-1` 的空心菱形。
**输入格式:** 输入菱形上半部分的高度。
**输出格式:** 空心菱形。
**数据范围:** `1≤n≤20`
**输入样例:**
```text
3
```
**输出样例:**
```text
*
* *
* *
* *
*
```
**解题思路:** 把每一行映射为到中间行的层数 `level`;输出前导空格、左边星号,宽度大于 1 时再输出内部空格和右边星号。
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n;
cin >> n;
for (int row = 1; row <= 2 * n - 1; row++) {
int level = n - abs(n - row);
int outer = n - level;
int inner = 2 * level - 3;
for (int j = 0; j < outer; j++) cout << " ";
cout << "*";
if (inner >= 0) {
for (int j = 0; j < inner; j++) cout << " ";
cout << "*";
}
cout << "\n";
}
return 0;
}
```
**易错提醒:** 顶点和底点只有一个星号,此时不能输出第二个星号。
### 扩展7组合沙漏【略难】
**题目描述:** 输入 `n`,打印由星号组成的实心沙漏。第一行有 `2n-1` 个星号,逐行减少两个到 1再逐行增加两个中间行只输出一次。
**输入格式:** 输入沙漏上半部分的高度。
**输出格式:** 实心沙漏。
**数据范围:** `1≤n≤20`
**输入样例:**
```text
3
```
**输出样例:**
```text
*****
***
*
***
*****
```
**解题思路:** 上半部分第 `i` 行输出 `i-1` 个空格和 `2(n-i)+1` 个星号;下半部分反向恢复。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
void printRow(int spaces, int stars) {
for (int j = 0; j < spaces; j++) cout << " ";
for (int j = 0; j < stars; j++) cout << "*";
cout << "\n";
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
printRow(i - 1, 2 * (n - i) + 1);
for (int i = n - 1; i >= 1; i--)
printRow(i - 1, 2 * (n - i) + 1);
return 0;
}
```
**易错提醒:** 下半部分从 `n-1` 开始,避免最窄的中间行重复。

View File

@@ -0,0 +1,144 @@
# 第8课字符数组与string
## 教学目标
- 掌握 string 类的基本操作(长度、访问、截取、查找)
- 理解字符数组与 string 的区别
- 能处理字符串遍历、逆序、回文判断等常见问题
- 会用 `getline` 读取含空格的字符串
## 核心考点:字符串输入和逐字符处理
### 考什么
正确读取单词或整行文字再逐个访问________完成统计、查找、替换或回文判断。
### 必须理解
`cin >> s` 遇到________就停止`getline(cin, s)` 会读取________。前面使用 `cin >>`通常先执行________清理残留换行。字符串合法下标是________。
### 核心写法
```cpp
cin >> n;
____________________;
string s;
____________________;
for (int i = 0; i < ____________________; i++) {
char c = ________;
}
```
### 常见错误
- 为什么 `cin >> s` 不能读完整句子________________
- `getline` 为什么有时会直接读到空行________________
- `i <= s.length()` 错在哪里________________
### 判断是否掌握
- [ ] 我能按输入格式选择读取方式。
- [ ] 我能处理 `cin` 后的残留换行。
- [ ] 我能无越界地逐字符处理字符串。
---
## 课堂编程题
### 课堂1两种字符串输入【适中】
**题目描述:** 第一行输入一个不含空格的单词,使用字符数组保存;第二行输入一个可能含空格的句子,使用字符串保存。依次输出单词长度、句子长度以及句子中的空格数量。
**输入格式:** 第一行输入一个不含空格的单词;第二行输入一整句话。
**输出格式:** 依次输出单词长度、句子长度和空格数量。
**数据范围:** 单词和句子长度均不超过 200。
**输入样例:**
```text
GESP
I love C++
```
**输出样例:**
```text
4 10 2
```
**解题思路:** 字符数组以 `'\0'` 作为结束标志;读完单词后先忽略换行,再用 `getline` 读取整行字符串。
**易错提醒:** `cin` 不会读入空格;`getline` 前要处理输入缓冲区中的换行符。
### 课堂2字符串分类统计【适中】
**题目描述:** 输入一整行字符串,统计大写字母、小写字母、数字、空格和其他字符的数量。
**输入格式:** 输入一整行字符串。
**输出格式:** 依次输出五类字符的数量。
**数据范围:** 字符串长度不超过 500。
**输入样例:**
```text
Hi 2026!
```
**输出样例:**
```text
1 1 4 1 1
```
**解题思路:** 使用下标遍历字符串,通过互斥分支判断字符所属类别。
**易错提醒:** 字符串的合法下标为 0 到长度减 1分类时只能让一个字符进入一个计数器。
### 课堂3敏感词替换与截取【适中】
**题目描述:** 输入一个不含空格的原字符串、待查找字符串和替换字符串。将原字符串中第一次出现的待查找内容替换掉,并输出替换结果;若未找到,输出 `NOT FOUND`。找到时还要输出替换位置之前的前缀。
**输入格式:** 依次输入原字符串、待查找字符串和替换字符串,三者均不含空格。
**输出格式:** 找到时第一行输出替换后的字符串,第二行输出原字符串中匹配位置之前的部分;未找到时输出 `NOT FOUND`
**数据范围:** 每个字符串长度不超过 200。
**输入样例:**
```text
abc123abc 123 XYZ
```
**输出样例:**
```text
abcXYZabc
abc
```
**解题思路:**`find` 获得首次位置;用 `replace` 替换,用 `substr` 截取前缀。
**易错提醒:** 未找到时 `find` 返回 `string::npos`,不能直接拿这个位置去替换或截取。
### 课堂4忽略格式的回文句【适中】
**题目描述:** 输入一整行字符串,忽略空格和英文字母大小写后,判断它是否为回文串。
**输入格式:** 输入一整行字符串。
**输出格式:** 是回文串输出 `YES`,否则输出 `NO`
**数据范围:** 字符串长度不超过 500只含英文字母和空格。
**输入样例:**
```text
Never odd or even
```
**输出样例:**
```text
YES
```
**解题思路:** 先构造一个无空格、全小写的新字符串,再用左右下标向中间比较。
**易错提醒:** 比较前必须统一大小写;空字符串和单字符都属于回文串。
---
## 课后作业
> 请完成本课对应的课后作业,见编程题文件。

View File

@@ -0,0 +1,301 @@
# 第8课字符数组与string
## 教学目标
- 掌握 string 类的基本操作(长度、访问、截取、查找)
- 理解字符数组与 string 的区别
- 能处理字符串遍历、逆序、回文判断等常见问题
- 会用 `getline` 读取含空格的字符串
## 核心考点:字符串输入和逐字符处理
### 考什么
常见题型包括读取一句话、统计各类字符、查找或替换子串、反转字符串和判断回文。首先要正确读入完整字符串,然后通过下标逐个处理字符。
### 必须理解
`cin >> s` 遇到空格就停止,只适合读取一个不含空格的单词;`getline(cin, s)` 会读取整行。如果前面刚用 `cin >>` 读取数字,输入缓冲区可能残留换行符,应先用 `cin.ignore()` 清掉,否则 `getline` 会读到空行。字符串长度为 `s.length()`,合法下标仍是 `0~length-1`。逐字符处理时,每个 `s[i]` 都是 `char`,可配合 `isdigit``isalpha` 等函数分类。
### 核心写法
```cpp
int n;
cin >> n;
cin.ignore();
string s;
getline(cin, s);
for (int i = 0; i < s.length(); i++) {
char c = s[i];
// 对 c 进行判断、统计或修改
}
```
输入方式由数据格式决定;遍历上界由字符串的实际长度决定。
### 常见错误
-`cin >> s` 读取句子,只得到第一个单词。
- 混用 `cin``getline` 时忘记处理残留换行。
- 写成 `i <= s.length()`,访问字符串末尾之外的位置。
- 把字符下标与子串位置混淆,或未检查 `find` 是否找到。
### 判断是否掌握
能根据输入是否含空格选择 `cin``getline`,能解释空行问题,并能无越界地遍历字符串完成字符统计或修改。
---
## 题目一:字符串输入输出与字符统计
### 核心代码(一句话)
> 字符串处理的核心:**`getline(cin, s)` 读整行****`s.length()` 取长度****`s[i]` 访问每个字符**,配合字符判断函数做统计。
### 自然语言思路
输入一句话,统计里面的大写字母、小写字母、数字、其他字符各有几个。
第一步,用 `getline(cin, s)` 读入整行(不能用 `cin >> s`,因为碰到空格就停了)。
第二步,用 `s.length()``s.size()` 拿到字符串长度。
第三步,用 `for` 循环遍历每个字符 `s[i]`
第四步,对每个字符用判断函数分类:`isupper()` 判断大写、`islower()` 判断小写、`isdigit()` 判断数字,剩下的就是"其他"。
第五步,四个计数器分别累加,最后输出。
> 提示:`isupper`、`islower`、`isdigit` 这些函数在第1课学过头文件是 `<cctype>`。
### 伪代码
```
1. 输入一行字符串 s用 getline
2. upper = 0, lower = 0, digit = 0, other = 0
3. for i = 0 到 s.length() - 1
4. if isupper(s[i]) upper++
5. else if islower(s[i]) lower++
6. else if isdigit(s[i]) digit++
7. else other++
8. 输出 upper, lower, digit, other
```
### 真实代码
```cpp
#include <iostream>
#include <string>
#include <cctype> // isupper, islower, isdigit
using namespace std;
int main() {
string s;
getline(cin, s); // 读整行,含空格
int upper = 0, lower = 0, digit = 0, other = 0;
for (int i = 0; i < s.length(); i++) {
if (isupper(s[i])) {
upper++;
} else if (islower(s[i])) {
lower++;
} else if (isdigit(s[i])) {
digit++;
} else {
other++;
}
}
cout << "大写:" << upper
<< ",小写:" << lower
<< ",数字:" << digit
<< ",其他:" << other << endl;
return 0;
}
```
### 注意点
1. **`cin >> s` vs `getline(cin, s)`**`cin` 遇空格换行就停,`getline` 读整行(含空格)。如果前面用过 `cin`,需要用 `cin.ignore()` 吃掉残留的换行符。
2. **`s.length()` vs `s.size()`**:两个完全一样,都可以用,没有区别。
3. **访问越界**:下标范围 `0``s.length() - 1`,访问 `s[s.length()]` 会越界。
4. **空格算"其他"**:上面的代码中空格既不是大写/小写/数字,会归入 other。如果想单独统计空格加一个 `else if (s[i] == ' ')`
---
## 题目二:字符串查找与替换
### 核心代码(一句话)
> 查找用 **`s.find("目标")`**,找不到返回 **`string::npos`**;替换用 **`s.replace(起始位置, 长度, "新内容")`**;截取用 **`s.substr(起始位置, 长度)`**。
### 自然语言思路
在一句话里找到某个词的位置,然后把它替换成另一个词。
第一步,用 `s.find("目标词")` 查找,它会返回目标词第一次出现的位置(下标)。
第二步,如果找到了(返回值 `!= string::npos`),用 `s.replace(位置, 长度, "新词")` 替换。
第三步,如果找不到,`find` 返回 `string::npos`(一个很大的特殊值),直接提示"没找到"。
第四步,`substr` 用来截取子串,比如 `s.substr(0, 5)` 表示从下标 0 开始取 5 个字符。
### 伪代码
```
1. 输入字符串 s
2. 输入要查找的词 target
3. pos = s.find(target)
4. if pos != string::npos
5. 输入替换词 replacement
6. s.replace(pos, target.length(), replacement)
7. 输出 s
8. else
9. 输出 "未找到"
```
### 真实代码
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
getline(cin, s);
string target;
cin >> target;
int pos = s.find(target);
if (pos != string::npos) {
string replacement;
cin >> replacement;
s.replace(pos, target.length(), replacement);
cout << s << endl;
} else {
cout << "未找到" << endl;
}
return 0;
}
```
### 注意点
1. **`string::npos` 是什么**`npos` = "no position",是一个特殊常量,表示"没找到"。不要用 `-1``0` 来判断。
2. **`replace` 会修改原字符串**`s.replace(...)` 直接改了 `s`,如果还需要原来的值,先备份。
3. **`substr` 参数**`s.substr(开始下标, 长度)`。如果省略第二个参数,默认截取到末尾。
4. **find 只找第一次出现**:如果想找所有出现的位置,需要循环 + 更新起始位置。
---
## 题目三:字符串逆序与回文判断
### 核心代码(一句话)
> 字符串逆序的核心:**从最后一个字符 `s.length()-1` 遍历到第一个字符 `0`**;回文判断的核心:**首尾字符两两比较,`s[i] != s[n-1-i]` 就不是回文**。
### 自然语言思路
**字符串逆序输出**
第一步,拿到最后一个字符的下标:`s.length() - 1`
第二步,从后往前遍历:`for (i = s.length()-1; i >= 0; i--)`
第三步,逐个输出 `s[i]`,就得到了逆序字符串。
**判断回文**
第一步,回文就是正着读和反着读一样,比如 "abcba"、"level"。
第二步,只需要比较前一半和后一半:`s[0]``s[n-1]``s[1]``s[n-2]`……直到中间。
第三步,用 `bool` 变量记录结果,默认 `true`。一旦发现不相等,改成 `false``break`
第四步,别忘了字符串可能有空格,但 GESP 题目一般用 `cin` 读单个词即可。
### 伪代码
**字符串逆序**
```
1. 输入 s
2. for i = s.length()-1 递减到 0
3. 输出 s[i]
4. 输出换行
```
**回文判断**
```
1. 输入 s
2. n = s.length()
3. isPalindrome = true
4. for i = 0 到 n/2 - 1
5. if s[i] != s[n-1-i]
6. isPalindrome = false
7. break
8. if isPalindrome
9. 输出 "是回文"
10. else
11. 输出 "不是回文"
```
### 真实代码
**字符串逆序**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s;
for (int i = s.length() - 1; i >= 0; i--) {
cout << s[i];
}
cout << endl;
return 0;
}
```
**回文判断**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s;
int n = s.length();
bool isPalindrome = true;
for (int i = 0; i < n / 2; i++) {
if (s[i] != s[n - 1 - i]) {
isPalindrome = false;
break;
}
}
if (isPalindrome) {
cout << "是回文" << endl;
} else {
cout << "不是回文" << endl;
}
return 0;
}
```
### 注意点
1. **逆序遍历注意边界**`for (i = s.length()-1; i >= 0; i--)`,不能忘了减 1否则越界。
2. **回文只需比一半**:循环 `n/2` 次就够了。如果 n=5比下标 (0,4) 和 (1,3),中间的下标 2 不用比。
3. **大小写问题**:严格判断回文时 "Aba" 不是回文A ≠ a。如果要忽略大小写可以先用 `tolower`/`toupper` 统一转换。
4. **string 可以直接用 `==` 比较**`s1 == s2` 判断两个字符串是否完全相等,比字符数组方便得多。
---
## 字符数组 vs string 速查
| 特性 | 字符数组 `char[]` | string 类 |
|------|-------------------|-----------|
| 头文件 | 不需要(或 `<cstring>` | `<string>` |
| 定义 | `char str[100];` | `string s;` |
| 输入 | `cin >> str;` | `cin >> s;` |
| 读整行 | `cin.getline(str, 100);` | `getline(cin, s);` |
| 长度 | `strlen(str)` | `s.length()``s.size()` |
| 拼接 | `strcat(str1, str2);` | `s1 + s2` |
| 比较 | `strcmp(str1, str2)` | `s1 == s2` |
| 结尾标志 | `'\0'` | 不需要关心 |
> **建议**GESP 考试中优先用 `string`,操作更简单,不容易出错。
---
## 本课打油诗
> string 类真好用,拼接查找样样通。
> getline 读整行length 长度记心中。
> 下标访问像数组,逆序只需倒着走。
> 回文首尾比一半find 定位快如风。
---
## 课后作业
1. 输入一个字符串,统计其中数字、字母、其他字符的个数
2. 输入一个字符串,删除其中所有的空格后输出
3. 输入两个字符串 A 和 B判断 B 是否是 A 的子串
4. 输入一个字符串,将其中的数字提取出来组成新字符串并输出

View File

@@ -0,0 +1,655 @@
# 第8课编程题教师版
## 本课知识点覆盖表
| 核心知识点 | GESP等级 | 对应课堂题 |
|---|---|---|
| 字符数组、字符串结束标志、单词与整行输入 | 三级工具 | 课堂1 |
| `string` 长度、下标遍历、字符分类统计 | 三级工具 | 课堂2 |
| 字符串查找、替换与截取 | 三级工具 | 课堂3 |
| 字符串逆序与回文判断 | 三级工具 | 课堂4 |
## 一、课堂练习4题
### 课堂1两种字符串输入【适中】
**题目描述:** 第一行输入一个不含空格的单词,使用字符数组保存;第二行输入一个可能含空格的句子,使用字符串保存。依次输出单词长度、句子长度以及句子中的空格数量。
**输入格式:** 第一行输入一个不含空格的单词;第二行输入一整句话。
**输出格式:** 依次输出单词长度、句子长度和空格数量。
**数据范围:** 单词和句子长度均不超过 200。
**输入样例:**
```text
GESP
I love C++
```
**输出样例:**
```text
4 10 2
```
**解题思路:** 字符数组以 `'\0'` 作为结束标志;读完单词后先忽略换行,再用 `getline` 读取整行字符串。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
char word[205];
string sentence;
cin >> word;
cin.ignore();
getline(cin, sentence);
int wordLength = 0;
while (word[wordLength] != '\0') wordLength++;
int spaces = 0;
for (char c : sentence) {
if (c == ' ') spaces++;
}
cout << wordLength << " " << sentence.size() << " " << spaces;
return 0;
}
```
**易错提醒:** `cin` 不会读入空格;`getline` 前要处理输入缓冲区中的换行符。
### 课堂2字符串分类统计【适中】
**题目描述:** 输入一整行字符串,统计大写字母、小写字母、数字、空格和其他字符的数量。
**输入格式:** 输入一整行字符串。
**输出格式:** 依次输出五类字符的数量。
**数据范围:** 字符串长度不超过 500。
**输入样例:**
```text
Hi 2026!
```
**输出样例:**
```text
1 1 4 1 1
```
**解题思路:** 使用下标遍历字符串,通过互斥分支判断字符所属类别。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
getline(cin, s);
int upper = 0, lower = 0, digit = 0, space = 0, other = 0;
for (int i = 0; i < (int)s.size(); i++) {
char c = s[i];
if (c >= 'A' && c <= 'Z') upper++;
else if (c >= 'a' && c <= 'z') lower++;
else if (c >= '0' && c <= '9') digit++;
else if (c == ' ') space++;
else other++;
}
cout << upper << " " << lower << " " << digit << " "
<< space << " " << other;
return 0;
}
```
**易错提醒:** 字符串的合法下标为 0 到长度减 1分类时只能让一个字符进入一个计数器。
### 课堂3敏感词替换与截取【适中】
**题目描述:** 输入一个不含空格的原字符串、待查找字符串和替换字符串。将原字符串中第一次出现的待查找内容替换掉,并输出替换结果;若未找到,输出 `NOT FOUND`。找到时还要输出替换位置之前的前缀。
**输入格式:** 依次输入原字符串、待查找字符串和替换字符串,三者均不含空格。
**输出格式:** 找到时第一行输出替换后的字符串,第二行输出原字符串中匹配位置之前的部分;未找到时输出 `NOT FOUND`
**数据范围:** 每个字符串长度不超过 200。
**输入样例:**
```text
abc123abc 123 XYZ
```
**输出样例:**
```text
abcXYZabc
abc
```
**解题思路:**`find` 获得首次位置;用 `replace` 替换,用 `substr` 截取前缀。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string source, target, replacement;
cin >> source >> target >> replacement;
size_t position = source.find(target);
if (position == string::npos) {
cout << "NOT FOUND";
} else {
string prefix = source.substr(0, position);
source.replace(position, target.size(), replacement);
cout << source << "\n" << prefix;
}
return 0;
}
```
**易错提醒:** 未找到时 `find` 返回 `string::npos`,不能直接拿这个位置去替换或截取。
### 课堂4忽略格式的回文句【适中】
**题目描述:** 输入一整行字符串,忽略空格和英文字母大小写后,判断它是否为回文串。
**输入格式:** 输入一整行字符串。
**输出格式:** 是回文串输出 `YES`,否则输出 `NO`
**数据范围:** 字符串长度不超过 500只含英文字母和空格。
**输入样例:**
```text
Never odd or even
```
**输出样例:**
```text
YES
```
**解题思路:** 先构造一个无空格、全小写的新字符串,再用左右下标向中间比较。
**参考代码:**
```cpp
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main() {
string s, cleaned;
getline(cin, s);
for (char c : s) {
if (c != ' ') cleaned += (char)tolower(c);
}
bool palindrome = true;
int left = 0, right = (int)cleaned.size() - 1;
while (left < right) {
if (cleaned[left] != cleaned[right]) {
palindrome = false;
break;
}
left++;
right--;
}
cout << (palindrome ? "YES" : "NO");
return 0;
}
```
**易错提醒:** 比较前必须统一大小写;空字符串和单字符都属于回文串。
## 二、课后作业3题
### 课后1字符串长度【简单】
**题目描述:** 输入一个不含空格的字符串,输出它的长度。
**输入格式:** 输入一个不含空格的字符串。
**输出格式:** 字符串长度。
**数据范围:** 字符串长度为 1200。
**输入样例:**
```text
orange
```
**输出样例:**
```text
6
```
**解题思路:** 使用字符串的 `size` 获取长度。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s;
cout << s.size();
return 0;
}
```
**易错提醒:** 长度是字符个数,最后一个字符的下标是长度减 1。
### 课后2统计指定字符【简单】
**题目描述:** 输入一个不含空格的字符串和一个字符,统计该字符出现次数。
**输入格式:** 输入一个字符串和一个待统计字符。
**输出格式:** 指定字符出现次数。
**数据范围:** 字符串长度不超过 200。
**输入样例:**
```text
banana a
```
**输出样例:**
```text
3
```
**解题思路:** 遍历字符串,字符相等时计数。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
char target;
int count = 0;
cin >> s >> target;
for (char c : s) {
if (c == target) count++;
}
cout << count;
return 0;
}
```
**易错提醒:** 字符使用单引号概念,字符串使用双引号概念。
### 课后3字符串逆序【简单】
**题目描述:** 输入一个不含空格的字符串,将它逆序输出。
**输入格式:** 输入一个不含空格的字符串。
**输出格式:** 逆序后的字符串。
**数据范围:** 字符串长度不超过 200。
**输入样例:**
```text
GESP
```
**输出样例:**
```text
PSEG
```
**解题思路:** 从最后一个下标向 0 倒序输出。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s;
for (int i = (int)s.size() - 1; i >= 0; i--) cout << s[i];
return 0;
}
```
**易错提醒:** 倒序循环变量应使用有符号整数,否则减到 0 以下时可能出错。
## 三、扩展练习7题
### 扩展1大小写交替转换【基础综合】
**题目描述:** 输入一个不含空格的字符串,把大写字母转成小写、小写字母转成大写,其他字符保持不变。
**输入格式:** 输入一个不含空格的字符串。
**输出格式:** 转换后的字符串。
**数据范围:** 字符串长度不超过 200。
**输入样例:**
```text
AbC123xY
```
**输出样例:**
```text
aBc123Xy
```
**解题思路:** 遍历并按 ASCII 范围判断,分别调用大小写转换函数。
**参考代码:**
```cpp
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main() {
string s;
cin >> s;
for (char &c : s) {
if (c >= 'A' && c <= 'Z') c = (char)tolower(c);
else if (c >= 'a' && c <= 'z') c = (char)toupper(c);
}
cout << s;
return 0;
}
```
**易错提醒:** 数字和符号不要改变。
### 扩展2删除指定字符【基础综合】
**题目描述:** 输入一整行字符串和一个字符,删除字符串中所有该字符后输出结果。
**输入格式:** 第一行输入一整行字符串;第二行输入待删除字符。
**输出格式:** 删除后的字符串。
**数据范围:** 原字符串长度不超过 500。
**输入样例:**
```text
a cat and a hat
a
```
**输出样例:**
```text
ct nd ht
```
**解题思路:** 构造新字符串,只加入不等于目标的字符。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s, answer;
char target;
getline(cin, s);
cin >> target;
for (char c : s) {
if (c != target) answer += c;
}
cout << answer;
return 0;
}
```
**易错提醒:** 删除字符后空格仍需保留。
### 扩展3单词数量【中等】
**题目描述:** 输入一整行英文句子,单词之间可能有多个空格,统计单词数量。
**输入格式:** 输入一整行英文句子。
**输出格式:** 单词数量。
**数据范围:** 句子长度不超过 500只含英文字母和空格。
**输入样例:**
```text
I love Cpp
```
**输出样例:**
```text
3
```
**解题思路:** 当当前位置是字母,且它是首字符或前一个字符为空格时,说明一个新单词开始。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
getline(cin, s);
int count = 0;
for (int i = 0; i < (int)s.size(); i++) {
if (s[i] != ' ' && (i == 0 || s[i - 1] == ' ')) count++;
}
cout << count;
return 0;
}
```
**易错提醒:** 不能用空格数加一,因为句首、句尾和连续空格都会造成错误。
### 扩展4查找所有位置【中等】
**题目描述:** 输入原字符串和一个字符,输出该字符出现的所有位置,位置从 1 开始;若没有出现,输出 `NONE`
**输入格式:** 输入一个不含空格的字符串和一个待查找字符。
**输出格式:** 所有出现位置或 `NONE`
**数据范围:** 字符串长度不超过 500。
**输入样例:**
```text
mississippi s
```
**输出样例:**
```text
3 4 6 7
```
**解题思路:** 完整遍历而不提前结束,每次匹配都输出当前位置加一。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
char target;
cin >> s >> target;
int count = 0;
for (int i = 0; i < (int)s.size(); i++) {
if (s[i] == target) {
if (count) cout << " ";
cout << i + 1;
count++;
}
}
if (count == 0) cout << "NONE";
return 0;
}
```
**易错提醒:** 题目要求所有位置,不能找到第一个后使用 `break`
### 扩展5连续字符压缩【中等】
**题目描述:** 输入一个仅含英文字母的字符串,将连续相同字符压缩为“字符加次数”。即使只出现一次,也要输出次数。
**输入格式:** 输入一个仅含英文字母且不含空格的字符串。
**输出格式:** 压缩后的字符串。
**数据范围:** 字符串长度为 1500。
**输入样例:**
```text
aaabbcaaaa
```
**输出样例:**
```text
a3b2c1a4
```
**解题思路:** 记录当前连续段的字符和长度;遇到不同字符时输出上一段,最后补输出末段。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s;
char current = s[0];
int count = 1;
for (int i = 1; i < (int)s.size(); i++) {
if (s[i] == current) {
count++;
} else {
cout << current << count;
current = s[i];
count = 1;
}
}
cout << current << count;
return 0;
}
```
**易错提醒:** 循环结束后最后一段尚未输出,必须单独处理。
### 扩展6最长回文前缀【略难】
**题目描述:** 输入一个不含空格的字符串,找出从第一个字符开始的最长回文子串并输出。
**输入格式:** 输入一个不含空格的字符串。
**输出格式:** 最长回文前缀。
**数据范围:** 字符串长度为 1200。
**输入样例:**
```text
abacdf
```
**输出样例:**
```text
aba
```
**解题思路:** 从整个字符串长度开始递减,检查前缀两端是否对称;第一个成功的就是最长答案。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s;
for (int length = (int)s.size(); length >= 1; length--) {
bool palindrome = true;
for (int left = 0, right = length - 1; left < right;
left++, right--) {
if (s[left] != s[right]) {
palindrome = false;
break;
}
}
if (palindrome) {
cout << s.substr(0, length);
break;
}
}
return 0;
}
```
**易错提醒:** 检查的是从下标 0 开始的前缀,不是任意位置的回文子串。
### 扩展7两串是否由相同字母组成【略难】
**题目描述:** 输入两个只含小写字母的字符串,判断它们是否包含完全相同的字母及出现次数,字符顺序可以不同。
**输入格式:** 输入两个只含小写字母且不含空格的字符串。
**输出格式:** 满足条件输出 `YES`,否则输出 `NO`
**数据范围:** 每个字符串长度不超过 500。
**输入样例:**
```text
listen silent
```
**输出样例:**
```text
YES
```
**解题思路:** 使用长度为 26 的计数数组;遍历第一个字符串时加一,遍历第二个时减一,最后全部为零才匹配。
**参考代码:**
```cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string a, b;
cin >> a >> b;
int count[26] = {0};
for (char c : a) count[c - 'a']++;
for (char c : b) count[c - 'a']--;
bool same = true;
for (int i = 0; i < 26; i++) {
if (count[i] != 0) {
same = false;
break;
}
}
cout << (same ? "YES" : "NO");
return 0;
}
```
**易错提醒:** 只比较字符串长度不够,必须比较每个字母的出现次数。

View File

@@ -0,0 +1,171 @@
# 第9课数组计数法学生版
## 教学目标
- 理解“数值作下标、次数存数组”的计数思想
- 掌握数组计数的核心写法
- 能用计数数组完成频次统计、最高频查找和去重升序输出
- 知道数组计数法的适用条件和下标范围限制
## 核心考点:数值映射为下标的数组计数思想
### 考什么
统计整数的出现次数寻找最高频数字或完成________和有序输出。
### 必须理解
用数组下标 x 表示________`cnt[x]` 表示________。每读到一次 x就执行________。数组大小必须覆盖题目给出的全部________。
### 核心写法
```cpp
int cnt[________] = {};
for (int i = 0; i < n; i++) {
int x;
cin >> x;
____________________;
}
for (int i = 0; i <= 100; i++) {
if (____________________) cout << i;
}
```
### 常见错误
- 范围是0100时数组为什么要开101________________
- 计数数组为什么必须清零________________
- 负数能否直接作为数组下标________
### 判断是否掌握
- [ ] 我能根据数据范围确定数组大小。
- [ ] 我能解释下标与计数值分别代表什么。
- [ ] 我能用计数数组完成频次或去重任务。
---
## 课前认识:什么是数组计数法
如果输入的数字都在 `0~100` 之间,可以准备一个 `cnt[101]` 数组。
- 数字 `x` 对应数组下标________
- `cnt[x]` 保存________________
- 每读到一个数字 x执行________________
---
## 课堂编程题
### 课堂1成绩频次表【适中】
**题目描述:** 输入若干名学生的成绩,按分数从小到大输出每个出现过的分数及人数。
**输入格式:** 第一行输入学生人数;第二行输入每名学生的成绩。
**输出格式:** 每行输出一个出现过的分数和对应人数。
**数据范围:** 学生人数为 11000成绩为 0100。
**输入样例:**
```text
6
80 90 80 75 90 80
```
**输出样例:**
```text
75 1
80 3
90 2
```
**解题思路:** 分数可直接作为下标;每读到一个分数,对应计数加一,最后从 0 遍历到 100。
**易错提醒:** 允许分数 100所以数组至少需要 101 个位置。
---
### 课堂2最受欢迎的号码【适中】
**题目描述:** 输入若干个 150 的号码,输出出现次数最多的号码及次数;若并列,输出较小号码。
**输入格式:** 第一行输入号码总数;第二行输入所有号码。
**输出格式:** 输出最受欢迎的号码及其出现次数。
**数据范围:** 号码总数为 11000。
**输入样例:**
```text
7
8 3 8 5 3 8 3
```
**输出样例:**
```text
3 3
```
**解题思路:** 先计数,再从小到大打擂台;只有次数严格更多才更新,便能保留并列时较小号码。
**易错提醒:** 更新条件不能写大于等于,否则并列时会留下较大号码。
---
### 课堂3去重并升序输出【适中】
**题目描述:** 输入若干个 0100 的整数,去掉重复数字并升序输出,同时输出不同数字的个数。
**输入格式:** 第一行输入整数数量;第二行输入所有整数。
**输出格式:** 第一行输出不同数字的个数;第二行升序输出不同数字。
**数据范围:** 整数数量为 11000。
**输入样例:**
```text
8
4 2 4 1 2 9 1 5
```
**输出样例:**
```text
5
1 2 4 5 9
```
**解题思路:** 计数后从小到大遍历下标,次数大于 0 就输出一次并增加不同数字个数。
**易错提醒:** 下标从小到大遍历时,答案天然有序。
---
### 课堂4温度统计【适中】
**题目描述:** 输入若干个 -2050 的整数温度,输出每个出现过的温度及次数,并输出出现次数最多的温度;并列取较低温度。
**输入格式:** 第一行输入记录数量;第二行输入所有温度。
**输出格式:** 先按温度从低到高输出频次表,最后一行输出最高频温度。
**数据范围:** 记录数量为 11000。
**输入样例:**
```text
6
-2 0 -2 3 0 -2
```
**输出样例:**
```text
-2 3
0 2
3 1
BEST -2
```
**解题思路:** 温度加 20 后映射到 070输出和比较时再把下标减 20 还原。
**易错提醒:** 负数不能直接作为数组下标;映射和还原必须使用相同偏移量。
---
## 课后作业
> 请完成本课对应的课后作业,见编程题文件。

View File

@@ -0,0 +1,283 @@
# 第9课数组计数法
## 教学目标
- 理解“数值作下标、次数存数组”的计数思想
- 掌握数组计数的核心写法 `cnt[x]++`
- 能用计数数组完成频次统计、最高频查找和去重升序输出
- 知道数组计数法的适用条件和下标范围限制
## 核心考点:数值映射为下标的数组计数思想
### 考什么
题目会给出一组范围明确的整数,要求统计频次、找出现次数最多的数、判断某数是否出现,或去重后按序输出。关键是不用两两比较,而是让每个数直接找到自己的计数位置。
### 必须理解
当数值 x 能作为合法下标时,用 `cnt[x]` 表示 x 出现的次数。每读到一次 x就执行 `cnt[x]++`。统计结束后,遍历计数数组即可获得所有频次;因为下标从小到大遍历,输出天然有序。使用前必须根据数据范围确定数组大小并清零。若存在负数,可在范围较小时整体偏移;若数值范围极大,就不适合直接开计数数组。
### 核心写法
```cpp
int cnt[101] = {};
for (int i = 0; i < n; i++) {
int x;
cin >> x;
cnt[x]++;
}
for (int value = 0; value <= 100; value++) {
if (cnt[value] > 0) {
cout << value << ' ' << cnt[value] << endl;
}
}
```
这里下标 `value` 代表具体数值,数组元素 `cnt[value]` 代表次数,两者含义不能混淆。
### 常见错误
- 数值范围到100却只开 `cnt[100]`无法访问下标100。
- 计数数组未清零,初始垃圾值会参与累加。
- 输入可能为负数或超范围,仍直接作为下标导致越界。
- 输出最高频数字时混淆“数字本身”和“出现次数”。
### 判断是否掌握
能根据数据范围确定计数数组大小,能解释 `cnt[x]++` 的含义,并能通过一次计数、一次遍历完成频次查询、众数或去重输出。
---
## 课前认识:什么是数组计数法
如果输入的数字都在 `0~100` 之间,可以准备一个 `cnt[101]` 数组:
- 数字 `x` 出现一次,就执行 `cnt[x]++`
- `cnt[3]` 表示数字 3 出现了几次
- `cnt[100]` 表示数字 100 出现了几次
例如输入 `3 1 3 2 1 3`,计数后有:
| 数字(下标) | 0 | 1 | 2 | 3 |
|---|---:|---:|---:|---:|
| 次数cnt | 0 | 2 | 1 | 3 |
> 数组计数法的核心,就是把数字直接放到对应编号的“小格子”里做记号。
---
## 题目一:统计数字出现次数
### 题目描述
输入 n 个 `0~100` 之间的整数,按数字从小到大的顺序,输出每个出现过的数字及其出现次数。
### 核心代码(一句话)
> 每读到一个数字 `x`,就让对应位置加一:`cnt[x]++`。
### 自然语言思路
第一步,准备 `cnt[101]`,把每个位置都初始化为 0。
第二步,循环读入 n 个数字。读到 `x`,就在编号为 x 的格子里加一次,也就是 `cnt[x]++`
第三步,从 0 遍历到 100。若 `cnt[i] > 0`,说明数字 i 出现过,输出 i 和它的次数。
### 伪代码
```text
1. 输入 n
2. 创建并清零计数数组 cnt[101]
3. 重复 n 次:
4. 输入 x
5. cnt[x] 加 1
6. 从 i = 0 遍历到 100
7. 如果 cnt[i] > 0
8. 输出 i 和 cnt[i]
```
### 真实代码
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int cnt[101] = {};
for (int i = 0; i < n; i++) {
int x;
cin >> x;
cnt[x]++;
}
for (int i = 0; i <= 100; i++) {
if (cnt[i] > 0) {
cout << i << ":" << cnt[i] << endl;
}
}
return 0;
}
```
### 注意点
- `int cnt[101] = {};` 会把整个数组清零。
- 数值范围是 `0~100`,所以数组需要 101 个位置。
- 输出时只输出 `cnt[i] > 0` 的数字。
- 输入值不能超出数组下标范围,否则会越界。
---
## 题目二:寻找出现次数最多的数字
### 题目描述
输入 n 个 `0~100` 之间的整数,输出出现次数最多的数字。如果多个数字出现次数相同,输出其中较小的数字。
### 核心代码(一句话)
> 先用 `cnt[x]++` 统计次数,再从小到大打擂台;只有次数严格更多时才更换答案。
### 自然语言思路
第一步,先像题目一一样统计每个数字出现的次数。
第二步,假设数字 0 暂时是擂主,用 `bestValue` 保存擂主数字,用 `bestCount` 保存它的出现次数。
第三步,从数字 1 遍历到 100。如果 `cnt[i] > bestCount`,说明 i 出现得更多,让 i 成为新擂主。
第四步,如果次数相同,不更换擂主。因为我们从小到大遍历,先成为擂主的数字更小,正好满足并列时取较小值。
### 伪代码
```text
1. 输入 n统计 cnt
2. bestValue = 0
3. bestCount = cnt[0]
4. 从 i = 1 遍历到 100
5. 如果 cnt[i] > bestCount
6. bestValue = i
7. bestCount = cnt[i]
8. 输出 bestValue
```
### 真实代码
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int cnt[101] = {};
for (int i = 0; i < n; i++) {
int x;
cin >> x;
cnt[x]++;
}
int bestValue = 0;
int bestCount = cnt[0];
for (int i = 1; i <= 100; i++) {
if (cnt[i] > bestCount) {
bestValue = i;
bestCount = cnt[i];
}
}
cout << bestValue << endl;
return 0;
}
```
### 注意点
- 题目保证 `n >= 1`,因此一定存在答案。
- 判断条件必须是 `>`,不能写成 `>=`;否则并列时会留下较大的数字。
- `bestValue` 保存数字,`bestCount` 保存次数,不要混淆。
- 这道题把第4课的“打擂台”和本课的数组计数结合了起来。
---
## 题目三:去重后升序输出
### 题目描述
输入 n 个 `0~100` 之间的整数,去掉重复数字,并按从小到大的顺序输出。
### 核心代码(一句话)
> 计数后从小到大遍历下标,`cnt[i] > 0` 就输出 i每个数字只输出一次。
### 自然语言思路
第一步,用计数数组记录每个数字是否出现过。
第二步,从 0 遍历到 100。数组下标本身就是数字而且遍历顺序天然是从小到大。
第三步,只要 `cnt[i] > 0`,就输出一次 i。不管它原来出现两次还是十次都只输出一次因此完成去重。
### 伪代码
```text
1. 输入 n统计 cnt
2. 从 i = 0 遍历到 100
3. 如果 cnt[i] > 0
4. 输出 i
```
### 真实代码
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int cnt[101] = {};
for (int i = 0; i < n; i++) {
int x;
cin >> x;
cnt[x]++;
}
bool first = true;
for (int i = 0; i <= 100; i++) {
if (cnt[i] > 0) {
if (!first) cout << ' ';
cout << i;
first = false;
}
}
cout << endl;
return 0;
}
```
### 注意点
- 遍历计数数组时,下标本身按从小到大变化,因此输出天然有序。
- 去重只关心“是否出现”,所以判断 `cnt[i] > 0` 即可。
- `first` 用来控制空格,避免行首或行尾出现多余空格。
- 如果数字范围非常大,例如到 `10^9`,就不适合直接开这么大的计数数组。
---
## 本课打油诗
> 数值下标来对号,
> 每次出现次数高。
> 从小到大逐格找,
> 统计去重一招好。
---
## 课后作业
1. 输入 n 个 `0~50` 的整数,再输入一个数字 q输出 q 出现了多少次。
2. 输入 n 个 `0~100` 的整数,输出出现次数最少且出现过的数字;并列时输出较小者。
3. 输入 n 个学生的分数(`0~100`依次输出每个分数段的人数不及格、60~79、80~89、90~100。
> 提示:先判断题目中的数字范围,再决定计数数组要开多大。

View File

@@ -0,0 +1,721 @@
# 第9课编程题教师版
## 本课知识点覆盖表
| 核心知识点 | 等级定位 | 对应课堂题 |
|---|---|---|
| 数值映射下标、计数数组初始化与边界 | 三级工具,服务一二级统计 | 课堂1 |
| 最高频打擂台与并列规则 | 三级工具,服务分支判断 | 课堂2 |
| 去重与按下标顺序输出 | 三级工具,服务循环遍历 | 课堂3 |
| 负数偏移映射与适用范围判断 | 三级工具,服务类型转换 | 课堂4 |
## 一、课堂练习4题
### 课堂1成绩频次表【适中】
**题目描述:** 输入若干名学生的成绩,按分数从小到大输出每个出现过的分数及人数。
**输入格式:** 第一行输入学生人数;第二行输入每名学生的成绩。
**输出格式:** 每行输出一个出现过的分数和对应人数。
**数据范围:** 学生人数为 11000成绩为 0100。
**输入样例:**
```text
6
80 90 80 75 90 80
```
**输出样例:**
```text
75 1
80 3
90 2
```
**解题思路:** 分数可直接作为下标;每读到一个分数,对应计数加一,最后从 0 遍历到 100。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count[101] = {0};
cin >> n;
for (int i = 0; i < n; i++) {
int score;
cin >> score;
count[score]++;
}
for (int score = 0; score <= 100; score++) {
if (count[score] > 0)
cout << score << " " << count[score] << "\n";
}
return 0;
}
```
**易错提醒:** 允许分数 100所以数组至少需要 101 个位置。
### 课堂2最受欢迎的号码【适中】
**题目描述:** 输入若干个 150 的号码,输出出现次数最多的号码及次数;若并列,输出较小号码。
**输入格式:** 第一行输入号码总数;第二行输入所有号码。
**输出格式:** 输出最受欢迎的号码及其出现次数。
**数据范围:** 号码总数为 11000。
**输入样例:**
```text
7
8 3 8 5 3 8 3
```
**输出样例:**
```text
3 3
```
**解题思路:** 先计数,再从小到大打擂台;只有次数严格更多才更新,便能保留并列时较小号码。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count[51] = {0};
cin >> n;
for (int i = 0; i < n; i++) {
int number;
cin >> number;
count[number]++;
}
int bestNumber = 1;
for (int number = 2; number <= 50; number++) {
if (count[number] > count[bestNumber])
bestNumber = number;
}
cout << bestNumber << " " << count[bestNumber];
return 0;
}
```
**易错提醒:** 更新条件不能写大于等于,否则并列时会留下较大号码。
### 课堂3去重并升序输出【适中】
**题目描述:** 输入若干个 0100 的整数,去掉重复数字并升序输出,同时输出不同数字的个数。
**输入格式:** 第一行输入整数数量;第二行输入所有整数。
**输出格式:** 第一行输出不同数字的个数;第二行升序输出不同数字。
**数据范围:** 整数数量为 11000。
**输入样例:**
```text
8
4 2 4 1 2 9 1 5
```
**输出样例:**
```text
5
1 2 4 5 9
```
**解题思路:** 计数后从小到大遍历下标,次数大于 0 就输出一次并增加不同数字个数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count[101] = {0}, kinds = 0;
cin >> n;
for (int i = 0; i < n; i++) {
int number;
cin >> number;
count[number]++;
}
for (int number = 0; number <= 100; number++)
if (count[number] > 0) kinds++;
cout << kinds << "\n";
bool first = true;
for (int number = 0; number <= 100; number++) {
if (count[number] > 0) {
if (!first) cout << " ";
cout << number;
first = false;
}
}
return 0;
}
```
**易错提醒:** 下标从小到大遍历时,答案天然有序。
### 课堂4温度统计【适中】
**题目描述:** 输入若干个 -2050 的整数温度,输出每个出现过的温度及次数,并输出出现次数最多的温度;并列取较低温度。
**输入格式:** 第一行输入记录数量;第二行输入所有温度。
**输出格式:** 先按温度从低到高输出频次表,最后一行输出最高频温度。
**数据范围:** 记录数量为 11000。
**输入样例:**
```text
6
-2 0 -2 3 0 -2
```
**输出样例:**
```text
-2 3
0 2
3 1
BEST -2
```
**解题思路:** 温度加 20 后映射到 070输出和比较时再把下标减 20 还原。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count[71] = {0};
cin >> n;
for (int i = 0; i < n; i++) {
int temperature;
cin >> temperature;
count[temperature + 20]++;
}
int best = 0;
for (int index = 0; index <= 70; index++) {
if (count[index] > 0)
cout << index - 20 << " " << count[index] << "\n";
if (count[index] > count[best]) best = index;
}
cout << "BEST " << best - 20;
return 0;
}
```
**易错提醒:** 负数不能直接作为数组下标;映射和还原必须使用相同偏移量。
## 二、课后作业3题
### 课后1查询出现次数【简单】
**题目描述:** 输入若干个 050 的整数和一个查询数字,输出该数字出现次数。
**输入格式:** 第一行输入整数数量;第二行输入所有整数;第三行输入查询数字。
**输出格式:** 查询数字的出现次数。
**数据范围:** 整数数量为 11000。
**输入样例:**
```text
5
1 3 3 8 3
3
```
**输出样例:**
```text
3
```
**解题思路:** 使用计数数组统计后,直接访问查询数字对应的位置。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count[51] = {0}, query;
cin >> n;
for (int i = 0; i < n; i++) {
int number;
cin >> number;
count[number]++;
}
cin >> query;
cout << count[query];
return 0;
}
```
**易错提醒:** 数字本身是下标,数组中保存的是次数。
### 课后2最少出现的数字【简单】
**题目描述:** 输入若干个 0100 的整数,输出出现次数最少且确实出现过的数字;并列取较小者。
**输入格式:** 第一行输入整数数量;第二行输入所有整数。
**输出格式:** 满足条件的数字和出现次数。
**数据范围:** 整数数量为 11000。
**输入样例:**
```text
6
2 2 3 4 4 4
```
**输出样例:**
```text
3 1
```
**解题思路:** 遍历时跳过次数为 0 的数字,再用打擂台寻找最小正次数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count[101] = {0}, answer = -1;
cin >> n;
for (int i = 0; i < n; i++) {
int number;
cin >> number;
count[number]++;
}
for (int number = 0; number <= 100; number++) {
if (count[number] == 0) continue;
if (answer == -1 || count[number] < count[answer])
answer = number;
}
cout << answer << " " << count[answer];
return 0;
}
```
**易错提醒:** 没出现过的数字次数为 0但不能参加“最少出现”比较。
### 课后3成绩分段【简单】
**题目描述:** 输入若干成绩依次统计不及格、6079、8089、90100 四个分数段的人数。
**输入格式:** 第一行输入学生人数;第二行输入每名学生的成绩。
**输出格式:** 依次输出四个分数段的人数。
**数据范围:** 学生人数为 11000成绩为 0100。
**输入样例:**
```text
6
55 60 79 80 90 100
```
**输出样例:**
```text
1 2 1 2
```
**解题思路:** 先按具体分数计数,再按四个区间累加频次。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count[101] = {0}, group[4] = {0};
cin >> n;
for (int i = 0; i < n; i++) {
int score;
cin >> score;
count[score]++;
}
for (int score = 0; score <= 100; score++) {
if (score < 60) group[0] += count[score];
else if (score < 80) group[1] += count[score];
else if (score < 90) group[2] += count[score];
else group[3] += count[score];
}
cout << group[0] << " " << group[1] << " "
<< group[2] << " " << group[3];
return 0;
}
```
**易错提醒:** 分数段边界必须首尾相接,不能遗漏 60、80、90 和 100。
## 三、扩展练习7题
### 扩展1缺少的号码【基础综合】
**题目描述:** 1若干号中每个号码本应出现一次实际记录中缺少一个号码。输出缺少的号码。
**输入格式:** 第一行输入完整号码范围的上界;第二行输入其余所有号码。
**输出格式:** 缺少的号码。
**数据范围:** 上界为 21000。
**输入样例:**
```text
5
1 2 5 3
```
**输出样例:**
```text
4
```
**解题思路:** 用计数数组记录出现情况,再寻找次数为 0 的号码。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int limit, count[1001] = {0};
cin >> limit;
for (int i = 1; i < limit; i++) {
int number;
cin >> number;
count[number]++;
}
for (int number = 1; number <= limit; number++) {
if (count[number] == 0) {
cout << number;
break;
}
}
return 0;
}
```
**易错提醒:** 实际输入数量比完整范围少 1。
### 扩展2字母频次表【基础综合】
**题目描述:** 输入一个只含大小写英文字母的字符串,忽略大小写,按字母顺序输出出现过的字母和次数。
**输入格式:** 输入一个不含空格的英文字母串。
**输出格式:** 每行输出一个小写字母和对应次数。
**数据范围:** 字符串长度为 1500。
**输入样例:**
```text
GespG
```
**输出样例:**
```text
e 1
g 2
p 1
s 1
```
**解题思路:** 先统一为小写,再用字母减去小写字母起点得到 025 的下标。
**参考代码:**
```cpp
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main() {
string s;
int count[26] = {0};
cin >> s;
for (char c : s) {
c = (char)tolower(c);
count[c - 'a']++;
}
for (int i = 0; i < 26; i++) {
if (count[i] > 0)
cout << (char)('a' + i) << " " << count[i] << "\n";
}
return 0;
}
```
**易错提醒:** 大写和小写统一后才能计入同一个位置。
### 扩展3只出现一次的最小数【中等】
**题目描述:** 输入若干个 01000 的整数,输出只出现一次的最小数字;不存在时输出 `NONE`
**输入格式:** 第一行输入整数数量;第二行输入所有整数。
**输出格式:** 满足条件的数字或 `NONE`
**数据范围:** 整数数量为 12000。
**输入样例:**
```text
7
5 2 5 3 8 2 9
```
**输出样例:**
```text
3
```
**解题思路:** 计数后从小到大寻找第一个次数等于 1 的下标。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count[1001] = {0}, answer = -1;
cin >> n;
for (int i = 0; i < n; i++) {
int number;
cin >> number;
count[number]++;
}
for (int number = 0; number <= 1000; number++) {
if (count[number] == 1) {
answer = number;
break;
}
}
if (answer == -1) cout << "NONE";
else cout << answer;
return 0;
}
```
**易错提醒:** “只出现一次”必须判断次数恰好等于 1。
### 扩展4公共数字【中等】
**题目描述:** 输入两组 0100 的整数,升序输出两组中都出现过的数字,每个数字只输出一次;没有则输出 `NONE`
**输入格式:** 第一行输入第一组数量和所有数字;第二行输入第二组数量和所有数字。
**输出格式:** 两组共有的不同数字。
**数据范围:** 每组数量为 11000。
**输入样例:**
```text
5 1 2 2 5 8
4 2 3 5 5
```
**输出样例:**
```text
2 5
```
**解题思路:** 分别用两个计数数组记录,两边次数都大于 0 的下标就是公共数字。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int firstCount[101] = {0}, secondCount[101] = {0};
int n, number;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> number;
firstCount[number]++;
}
cin >> n;
for (int i = 0; i < n; i++) {
cin >> number;
secondCount[number]++;
}
bool found = false;
for (int value = 0; value <= 100; value++) {
if (firstCount[value] > 0 && secondCount[value] > 0) {
if (found) cout << " ";
cout << value;
found = true;
}
}
if (!found) cout << "NONE";
return 0;
}
```
**易错提醒:** 只判断是否出现,不按较小次数重复输出。
### 扩展5两数之和查询【中等】
**题目描述:** 输入若干个 01000 的整数和目标和,判断能否从中选出两个不同位置的数,使它们之和等于目标。
**输入格式:** 第一行输入整数数量和目标和;第二行输入所有整数。
**输出格式:** 可以则输出 `YES`,否则输出 `NO`
**数据范围:** 整数数量为 22000目标和为 02000。
**输入样例:**
```text
5 10
2 8 4 4 7
```
**输出样例:**
```text
YES
```
**解题思路:** 计数后枚举第一个数,检查补数是否出现;两个数相同时必须至少出现两次。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, target, count[1001] = {0};
cin >> n >> target;
for (int i = 0; i < n; i++) {
int number;
cin >> number;
count[number]++;
}
bool found = false;
for (int value = 0; value <= 1000; value++) {
int other = target - value;
if (other < 0 || other > 1000) continue;
if (value != other && count[value] > 0 && count[other] > 0)
found = true;
if (value == other && count[value] >= 2)
found = true;
}
cout << (found ? "YES" : "NO");
return 0;
}
```
**易错提醒:** “不同位置”允许数值相同,但该数必须出现至少两次。
### 扩展6频次最接近目标【略难】
**题目描述:** 输入若干个 0100 的整数和目标次数,找出出现次数与目标次数差值最小且确实出现过的数字;并列取较小数字。
**输入格式:** 第一行输入整数数量和目标次数;第二行输入所有整数。
**输出格式:** 满足条件的数字及实际出现次数。
**数据范围:** 整数数量为 11000目标次数为 01000。
**输入样例:**
```text
8 3
1 1 2 2 2 2 5 5
```
**输出样例:**
```text
1 2
```
**解题思路:** 计数后对出现过的数字进行二次打擂台,比较频次与目标的距离。
**参考代码:**
```cpp
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n, target, count[101] = {0}, answer = -1;
cin >> n >> target;
for (int i = 0; i < n; i++) {
int number;
cin >> number;
count[number]++;
}
for (int value = 0; value <= 100; value++) {
if (count[value] == 0) continue;
if (answer == -1 ||
abs(count[value] - target) < abs(count[answer] - target))
answer = value;
}
cout << answer << " " << count[answer];
return 0;
}
```
**易错提醒:** 没出现的数字不能参加比较;从小到大遍历且并列不更新即可保留较小数字。
### 扩展7最多能配成多少对【略难】
**题目描述:** 输入若干只手套的颜色编号,每两只相同颜色手套可以配成一对。输出可配成的总对数、剩余单只数量,并按颜色升序输出每种颜色的对数。
**输入格式:** 第一行输入手套数量;第二行输入每只手套的颜色编号。
**输出格式:** 第一行输出总对数和剩余数量;随后输出每种能配对颜色的编号和对数。
**数据范围:** 手套数量为 12000颜色编号为 1100。
**输入样例:**
```text
8
1 1 1 2 2 3 3 3
```
**输出样例:**
```text
3 2
1 1
2 1
3 1
```
**解题思路:** 每种颜色的对数是次数除以 2剩余数是次数除以 2 的余数,再分别累加。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, count[101] = {0}, pairs = 0, single = 0;
cin >> n;
for (int i = 0; i < n; i++) {
int color;
cin >> color;
count[color]++;
}
for (int color = 1; color <= 100; color++) {
pairs += count[color] / 2;
single += count[color] % 2;
}
cout << pairs << " " << single << "\n";
for (int color = 1; color <= 100; color++) {
if (count[color] / 2 > 0)
cout << color << " " << count[color] / 2 << "\n";
}
return 0;
}
```
**易错提醒:** 同一颜色出现三次只能组成一对并剩下一只。

View File

@@ -0,0 +1,145 @@
# 第8课枚举与模拟三级工具降维
> 等级说明:本课使用三级工具帮助解决一级、二级综合问题。
## 教学目标
- 理解枚举算法的核心思想:列举所有可能,逐一验证
- 掌握枚举优化的两个技巧:缩小范围、提前终止
- 理解模拟算法的核心思想:按题目描述一步步翻译成代码
## 核心考点:枚举范围与模拟执行顺序
### 考什么
枚举所有可能并逐一________或按题目规则一步一步更新________。
### 必须理解
枚举三步是确定________、确定合法________、写出验证________。模拟时必须保持题目规定的________顺序。是否使用 `break`取决于题目要________________。
### 核心写法
```cpp
for (int i = ________; i <= ________; i++) {
if (____________________) {
// 记录、计数或输出
}
}
for (int i = 1; i <= ________; i++) {
// ____________________
}
```
### 常见错误
- 枚举范围漏掉端点会怎样________________
- 什么情况下不能找到答案就 `break`________________
- 模拟步骤颠倒是否会影响答案________
### 判断是否掌握
- [ ] 我能确定候选变量和完整范围。
- [ ] 我能写出验证条件。
- [ ] 我能按正确顺序模拟状态变化。
---
## 课堂编程题
### 课堂1鸡兔同笼【适中】
**题目描述:** 笼中只有鸡和兔。输入头数与脚数,输出鸡和兔的数量;没有合法方案时输出 `NONE`
**输入格式:** 输入动物总数和脚的总数。
**输出格式:** 输出鸡的数量和兔的数量,或输出 `NONE`
**数据范围:** 动物总数为 11000脚数为非负整数。
**输入样例:**
```text
10 28
```
**输出样例:**
```text
6 4
```
**解题思路:** 枚举鸡的数量,兔的数量由总数减去鸡数得到,再验证脚数。
**易错提醒:** 枚举范围必须包含 0 和动物总数;题目只需一个方案,找到后可以结束循环。
### 课堂2硬币组合【适中】
**题目描述:** 用 1 元、2 元和 5 元硬币凑出指定金额,要求硬币总数固定。输出所有方案,每行依次输出三种硬币数量。
**输入格式:** 输入目标金额和硬币总数。
**输出格式:** 输出全部合法方案;没有方案时输出 `NONE`
**数据范围:** 目标金额和硬币总数均为 1100。
**输入样例:**
```text
10 4
```
**输出样例:**
```text
1 2 1
```
**解题思路:** 枚举 1 元和 2 元硬币数量5 元硬币数量由总数直接计算,再检查金额。
**易错提醒:** 第三种硬币无需再枚举;题目要求全部方案,找到后不能提前结束。
### 课堂3斐波那契数列【适中】
**题目描述:** 数列前两项为 1之后每项等于前两项之和。输入项数输出前若干项。
**输入格式:** 输入需要输出的项数。
**输出格式:** 输出数列前若干项,以空格分隔。
**数据范围:** 项数为 145。
**输入样例:**
```text
6
```
**输出样例:**
```text
1 1 2 3 5 8
```
**解题思路:** 用两个变量保存连续两项;先计算下一项,再按顺序更新两个变量。
**易错提醒:** 更新顺序不能颠倒,必须先用旧值计算下一项。
### 课堂4交通灯状态【适中】
**题目描述:** 交通灯按"绿灯 30 秒、黄灯 5 秒、红灯 25 秒"循环。第 1 秒为绿灯。输入秒数,输出当时颜色以及该颜色已经持续的秒数。
**输入格式:** 输入一个正整数,表示经过的秒数。
**输出格式:** 输出颜色和当前颜色已持续的秒数。
**数据范围:** 秒数不超过十亿。
**输入样例:**
```text
34
```
**输出样例:**
```text
YELLOW 4
```
**解题思路:** 一个周期共 60 秒,将时间映射到 160再按三个区间判断。
**易错提醒:** 第 60 秒仍在本周期,第 61 秒才回到绿灯第 1 秒。
---
## 课后作业
> 请完成本课对应的课后作业,见编程题文件。

View File

@@ -0,0 +1,227 @@
# 第8课枚举与模拟三级工具降维
> 等级说明:枚举与模拟属于 GESP 三级知识。本课把它们作为工具,用来简化一级、二级的循环、分支与简单数学问题。
## 教学目标
- 理解枚举算法的核心思想:列举所有可能,逐一验证
- 掌握枚举优化的两个技巧:缩小范围、提前终止
- 理解模拟算法的核心思想:按题目描述一步步翻译成代码
## 核心考点:枚举范围与模拟执行顺序
### 考什么
枚举题要求列出所有可能答案并逐一验证;模拟题要求按照题目描述复现一个变化过程。两者都依赖清晰的范围、条件和执行顺序,常考输出满足条件的方案、计数或最终状态。
### 必须理解
枚举包含三步:确定候选变量、确定合法范围、写出验证条件。范围必须覆盖所有可能答案,但可以利用题目约束缩小。找到答案后是否 `break`,取决于题目要第一个答案还是全部答案。模拟则要把文字规则拆成按顺序执行的动作,明确每一步读取旧状态还是更新后的状态;若多个变量应同时变化,不能让前一个更新意外影响后一个计算。
### 核心写法
```cpp
for (int candidate = lower; candidate <= upper; candidate++) {
if () {
// 记录、计数或输出 candidate
}
}
for (int step = 1; step <= totalSteps; step++) {
// 按题意顺序更新当前状态
}
```
枚举是在“答案空间”中找;模拟是在“时间步骤”中走。先判断题目属于哪一种,再确定循环变量的含义。
### 常见错误
- 枚举范围少一个端点,漏掉合法答案。
- 未判断题目要全部解,找到第一个就错误 `break`
- 把模拟步骤顺序颠倒,得到不同状态。
- 明明能由约束缩小范围,却枚举过多无效候选,造成超时。
### 判断是否掌握
能指出循环变量代表候选答案还是执行步数,能给出完整上下界和验证条件,并能用手算前几步检查模拟更新顺序。
---
## 题目一:鸡兔同笼(枚举入门)
### 核心代码(一句话)
> 枚举的核心:**`for` 循环遍历所有可能的答案**,用 `if` 判断是否满足条件,满足就输出。
### 自然语言思路
笼子里有鸡和兔,一共 n 个头、m 只脚。鸡有 2 只脚,兔有 4 只脚。问鸡和兔各有几只?
第一步,鸡的数量可能是 0 到 n因为总共 n 个头),让 `chicken` 从 0 循环到 n。
第二步,兔的数量 = 总数 - 鸡数:`rabbit = n - chicken`
第三步,验证脚数:`chicken * 2 + rabbit * 4 == m`,如果成立,这就是答案。
第四步,输出鸡和兔的数量。
> 枚举就像"地毯式搜索"——把所有可能性都试一遍,看哪个符合条件。
### 伪代码
```
1. 输入 n头数, m脚数
2. for chicken = 0 到 n
3. rabbit = n - chicken
4. if chicken * 2 + rabbit * 4 == m
5. 输出 chicken 和 rabbit
```
### 真实代码
```cpp
#include <iostream>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
for (int chicken = 0; chicken <= n; chicken++) {
int rabbit = n - chicken;
if (chicken * 2 + rabbit * 4 == m) {
cout << "鸡:" << chicken << ",兔:" << rabbit << endl;
}
}
return 0;
}
```
### 注意点
1. **枚举范围 0 到 n**:不要漏掉 0可能全是兔子和 n可能全是鸡
2. **兔的数量直接算**:因为鸡兔总数固定,所以兔 = n - 鸡,不需要再套一层循环枚举兔。这是"间接枚举"——少一个变量就少一层循环。
3. **可能无解**:如果循环结束都没输出,说明数据有问题(比如输入 2 个头 100 只脚)。
4. **脚数奇偶性判断**:实际上可以先用 `if (m % 2 != 0)` 快速判断无解(因为每只动物脚数都是偶数),但这是优化技巧,初学者先掌握枚举思路。
---
## 题目二:找完数(枚举进阶)
### 核心代码(一句话)
> 完数判断的核心:**枚举所有小于 n 的正整数 i如果 `n % i == 0`i 就是 n 的因子,把因子累加起来**,最后比较和是否等于 n。
### 自然语言思路
完数是指一个数等于它所有真因子(不包括自身)之和。比如 6 = 1 + 2 + 328 = 1 + 2 + 4 + 7 + 14。请找出 1000 以内的所有完数。
第一步,外层枚举:`for (n = 2; n <= 1000; n++)`,对每个 n 判断它是不是完数。
第二步,对每个 n用内层循环从 1 到 n-1 找它的因子:`if (n % i == 0)``sum += i`
第三步,遍历完所有因子后,`if (sum == n)` 则 n 是完数,输出。
第四步,优化:找因子只需要搜到 `n/2`(因为大于 n/2 的数不可能是 n 的因子,除了 n 自身)。不过用 `sqrt(n)` 也行但要处理成对出现的情况。
### 伪代码
```
1. for n = 2 到 1000
2. sum = 0
3. for i = 1 到 n-1
4. if n % i == 0
5. sum += i
6. if sum == n
7. 输出 n
```
### 真实代码
```cpp
#include <iostream>
using namespace std;
int main() {
for (int n = 2; n <= 1000; n++) {
int sum = 0;
// 找因子并累加(优化:只需搜到 n/2
for (int i = 1; i <= n / 2; i++) {
if (n % i == 0) {
sum += i;
}
}
if (sum == n) {
cout << n << " "; // 输出6 28 496
}
}
cout << endl;
return 0;
}
```
### 注意点
1. **sum 必须在内层循环前清零**:每次换一个 nsum 要重新从 0 开始累加,忘了清零会累加前面所有数的结果。
2. **优化枚举范围**:因子搜索范围从 `n-1` 优化到 `n/2`,因为大于 n/2 且小于 n 的数不可能是 n 的因子。
3. **1 不是完数**1 的真因子之和是 01 没有真因子),不等于 1所以从 2 开始枚举。
4. **1000 以内的完数只有 3 个**6、28、496。如果输出不止这些说明逻辑有误。
---
## 题目三:斐波那契数列(模拟算法)
### 核心代码(一句话)
> 模拟的核心:**按照规则,用变量记录当前状态,一步一步更新**。斐波那契的规则是:**第三项 = 前两项之和**,用 `a, b` 两个变量滚动更新即可。
### 自然语言思路
斐波那契数列1, 1, 2, 3, 5, 8, 13, 21, 34, 55……每一项等于前两项之和。输入 n输出前 n 项。
第一步,用两个变量 `a``b` 分别记录"当前项"和"下一项",初始 `a = 1, b = 1`
第二步,循环 n 次,每次输出 `a`(当前项),然后计算新的两项:`temp = a + b; a = b; b = temp;`
第三步模拟过程a=1,b=1 → 输出1 → a=1,b=2 → 输出1 → a=2,b=3 → 输出2 → a=3,b=5 → 输出3 …… 就像"滚动更新"。
> 模拟算法 = 读懂题目规则 → 翻译成代码。不需要聪明,只需要认真。
### 伪代码
```
1. 输入 n
2. a = 1, b = 1
3. for i = 1 到 n
4. 输出 a
5. temp = a + b
6. a = b
7. b = temp
```
### 真实代码
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int a = 1, b = 1;
for (int i = 1; i <= n; i++) {
cout << a << " ";
int temp = a + b; // 计算下一项
a = b; // a 前进
b = temp; // b 前进
}
cout << endl;
return 0;
}
```
### 注意点
1. **更新顺序不能错**:必须先算 `temp = a + b`(基于旧值),再更新 a 和 b。如果先改 a 再算 `a + b`,用的就是新的 a 了,结果全乱。
2. **n=1 和 n=2 的情况**:循环 1 次输出 1循环 2 次输出 "1 1"。代码不用特殊处理,天然正确。
3. **数据范围**:斐波那契数列增长很快,第 46 项就超过 20 亿了。如果 n 大到 50+,要用 `long long` 替代 `int`(因为 `int` 最大约 21 亿)。
4. **另一种写法 — 数组存储**`int fib[100]; fib[1]=1; fib[2]=1; fib[i]=fib[i-1]+fib[i-2];` 更直观但占内存。
---
## 枚举 vs 模拟 对比
| 对比维度 | 枚举 | 模拟 |
|----------|------|------|
| 核心思路 | 列举所有可能 → 逐一验证 | 按照规则 → 逐步执行 |
| 适用场景 | 答案范围有限、可以逐一验证 | 题目有明确的步骤描述 |
| 典型题目 | 鸡兔同笼、百钱百鸡、完数 | 斐波那契、杨辉三角、日期计算 |
| 优化方向 | 缩小范围、提前终止、剪枝 | 找规律、用公式替代逐步模拟 |
---
## 本课打油诗
> 枚举就是全试过,循环里面 if 判断。
> 范围要想清边界,找到答案就停站。
> 模拟照着步骤走,题目说啥咱干啥。
> 变量更新看顺序,一步一步不出错。
---
## 课后作业
1. 找出 1000 以内所有的"亲密数对"a 的因子之和等于 bb 的因子之和等于 a且 a ≠ b
2. 百钱百鸡:公鸡 5 元/只,母鸡 3 元/只,小鸡 1 元 3 只。用 100 元买 100 只鸡,输出所有方案
3. 输出杨辉三角的前 n 行
4. 输出所有三位数的水仙花数(如 153 = 1³ + 5³ + 3³

View File

@@ -0,0 +1,676 @@
# 第8课编程题教师版
## 本课知识点覆盖表
| 核心知识点 | 等级定位 | 对应课堂题 |
|---|---|---|
| 单变量枚举、范围与验证条件 | 三级工具,服务一二级循环分支 | 课堂1 |
| 双变量枚举、利用约束缩小范围 | 三级工具,服务简单数学 | 课堂2 |
| 状态变量、按顺序更新 | 三级工具,服务循环模拟 | 课堂3 |
| 周期过程模拟、取模与分支 | 三级工具,服务综合判断 | 课堂4 |
## 一、课堂练习4题
### 课堂1鸡兔同笼【适中】
**题目描述:** 笼中只有鸡和兔。输入头数与脚数,输出鸡和兔的数量;没有合法方案时输出 `NONE`
**输入格式:** 输入动物总数和脚的总数。
**输出格式:** 输出鸡的数量和兔的数量,或输出 `NONE`
**数据范围:** 动物总数为 11000脚数为非负整数。
**输入样例:**
```text
10 28
```
**输出样例:**
```text
6 4
```
**解题思路:** 枚举鸡的数量,兔的数量由总数减去鸡数得到,再验证脚数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int heads, feet;
cin >> heads >> feet;
bool found = false;
for (int chicken = 0; chicken <= heads; chicken++) {
int rabbit = heads - chicken;
if (chicken * 2 + rabbit * 4 == feet) {
cout << chicken << " " << rabbit;
found = true;
break;
}
}
if (!found) cout << "NONE";
return 0;
}
```
**易错提醒:** 枚举范围必须包含 0 和动物总数;题目只需一个方案,找到后可以结束循环。
### 课堂2硬币组合【适中】
**题目描述:** 用 1 元、2 元和 5 元硬币凑出指定金额,要求硬币总数固定。输出所有方案,每行依次输出三种硬币数量。
**输入格式:** 输入目标金额和硬币总数。
**输出格式:** 输出全部合法方案;没有方案时输出 `NONE`
**数据范围:** 目标金额和硬币总数均为 1100。
**输入样例:**
```text
10 4
```
**输出样例:**
```text
1 2 1
```
**解题思路:** 枚举 1 元和 2 元硬币数量5 元硬币数量由总数直接计算,再检查金额。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int money, total;
cin >> money >> total;
bool found = false;
for (int one = 0; one <= total; one++) {
for (int two = 0; two + one <= total; two++) {
int five = total - one - two;
if (one + two * 2 + five * 5 == money) {
cout << one << " " << two << " " << five << "\n";
found = true;
}
}
}
if (!found) cout << "NONE";
return 0;
}
```
**易错提醒:** 第三种硬币无需再枚举;题目要求全部方案,找到后不能提前结束。
### 课堂3斐波那契数列【适中】
**题目描述:** 数列前两项为 1之后每项等于前两项之和。输入项数输出前若干项。
**输入格式:** 输入需要输出的项数。
**输出格式:** 输出数列前若干项,以空格分隔。
**数据范围:** 项数为 145。
**输入样例:**
```text
6
```
**输出样例:**
```text
1 1 2 3 5 8
```
**解题思路:** 用两个变量保存连续两项;先计算下一项,再按顺序更新两个变量。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
long long first = 1, second = 1;
cin >> n;
for (int i = 1; i <= n; i++) {
if (i > 1) cout << " ";
cout << first;
long long next = first + second;
first = second;
second = next;
}
return 0;
}
```
**易错提醒:** 更新顺序不能颠倒,必须先用旧值计算下一项。
### 课堂4交通灯状态【适中】
**题目描述:** 交通灯按“绿灯 30 秒、黄灯 5 秒、红灯 25 秒”循环。第 1 秒为绿灯。输入秒数,输出当时颜色以及该颜色已经持续的秒数。
**输入格式:** 输入一个正整数,表示经过的秒数。
**输出格式:** 输出颜色和当前颜色已持续的秒数。
**数据范围:** 秒数不超过十亿。
**输入样例:**
```text
34
```
**输出样例:**
```text
YELLOW 4
```
**解题思路:** 一个周期共 60 秒,将时间映射到 160再按三个区间判断。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
long long second;
cin >> second;
int position = (second - 1) % 60 + 1;
if (position <= 30)
cout << "GREEN " << position;
else if (position <= 35)
cout << "YELLOW " << position - 30;
else
cout << "RED " << position - 35;
return 0;
}
```
**易错提醒:** 第 60 秒仍在本周期,第 61 秒才回到绿灯第 1 秒。
## 二、课后作业3题
### 课后1满足条件的倍数【简单】
**题目描述:** 输出指定范围内所有能被 3 整除但不能被 5 整除的整数。
**输入格式:** 输入范围的左端点和右端点。
**输出格式:** 按从小到大输出所有满足条件的整数;没有则输出 `NONE`
**数据范围:** 左端点不大于右端点,绝对值不超过一万。
**输入样例:**
```text
1 20
```
**输出样例:**
```text
3 6 9 12 18
```
**解题思路:** 枚举范围内每个整数,用一个条件验证。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int left, right;
bool found = false;
cin >> left >> right;
for (int number = left; number <= right; number++) {
if (number % 3 == 0 && number % 5 != 0) {
if (found) cout << " ";
cout << number;
found = true;
}
}
if (!found) cout << "NONE";
return 0;
}
```
**易错提醒:** 范围包含两个端点;两个条件必须同时满足。
### 课后2小球反弹高度【简单】
**题目描述:** 小球从指定高度落下,每次反弹到上一次高度的一半。输入初始高度和反弹次数,输出最后一次反弹高度,保留两位小数。
**输入格式:** 输入初始高度和反弹次数。
**输出格式:** 输出最后一次反弹高度,保留两位小数。
**数据范围:** 初始高度为正数,反弹次数为 030。
**输入样例:**
```text
100 3
```
**输出样例:**
```text
12.50
```
**解题思路:** 每次循环把当前高度除以 2按步骤模拟。
**参考代码:**
```cpp
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double height;
int times;
cin >> height >> times;
for (int i = 1; i <= times; i++) height /= 2;
cout << fixed << setprecision(2) << height;
return 0;
}
```
**易错提醒:** 反弹 0 次时高度保持初始值。
### 课后3机器人走直线【简单】
**题目描述:** 机器人初始位置为 0。输入若干条指令正数表示向右移动负数表示向左移动。输出最终位置。
**输入格式:** 第一行输入指令数量;第二行输入每条指令的移动距离。
**输出格式:** 输出机器人最终位置。
**数据范围:** 指令数量为 1100每次移动距离绝对值不超过一千。
**输入样例:**
```text
5
3 -2 4 -6 1
```
**输出样例:**
```text
0
```
**解题思路:** 用一个变量记录位置,按输入顺序累加每次移动。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, position = 0;
cin >> n;
for (int i = 0; i < n; i++) {
int move;
cin >> move;
position += move;
}
cout << position;
return 0;
}
```
**易错提醒:** 负数已经表示向左,不需要再额外减一次。
## 三、扩展练习7题
### 扩展1三位水仙花数【基础综合】
**题目描述:** 输出指定范围内所有三位水仙花数。水仙花数等于各位数字立方和。
**输入格式:** 输入范围的左端点和右端点。
**输出格式:** 输出所有水仙花数;没有则输出 `NONE`
**数据范围:** 输入范围位于 100999。
**输入样例:**
```text
100 400
```
**输出样例:**
```text
153 370 371
```
**解题思路:** 枚举每个三位数,拆出百位、十位和个位后验证。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int left, right;
bool found = false;
cin >> left >> right;
for (int number = left; number <= right; number++) {
int a = number / 100;
int b = number / 10 % 10;
int c = number % 10;
if (a * a * a + b * b * b + c * c * c == number) {
if (found) cout << " ";
cout << number;
found = true;
}
}
if (!found) cout << "NONE";
return 0;
}
```
**易错提醒:** 十位数字应先除以 10 再取余数。
### 扩展2百钱买百鸡【中等】
**题目描述:** 公鸡每只 5 元,母鸡每只 3 元,小鸡 3 只 1 元。用 100 元恰好买 100 只鸡,输出全部方案。
**输入格式:** 本题没有输入。
**输出格式:** 每行依次输出公鸡、母鸡和小鸡数量。
**数据范围:** 三种鸡的数量均为非负整数。
**输入样例:**
```text
```
**输出样例:**
```text
0 25 75
4 18 78
8 11 81
12 4 84
```
**解题思路:** 枚举公鸡和母鸡,小鸡由总数计算;为避免小数,用“小鸡数量能被 3 整除”验证价格。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
for (int rooster = 0; rooster <= 20; rooster++) {
for (int hen = 0; hen <= 33; hen++) {
int chick = 100 - rooster - hen;
if (chick >= 0 && chick % 3 == 0 &&
rooster * 5 + hen * 3 + chick / 3 == 100) {
cout << rooster << " " << hen << " " << chick << "\n";
}
}
}
return 0;
}
```
**易错提醒:** 小鸡数量必须非负且能被 3 整除。
### 扩展3数字谜【中等】
**题目描述:** 找出所有两位数,使它等于各位数字之和的 4 倍,并按从小到大输出。
**输入格式:** 本题没有输入。
**输出格式:** 输出全部满足条件的两位数。
**数据范围:** 枚举 1099。
**输入样例:**
```text
```
**输出样例:**
```text
12 24 36 48
```
**解题思路:** 枚举两位数并拆分十位和个位,验证等式。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
bool first = true;
for (int number = 10; number <= 99; number++) {
int tens = number / 10;
int ones = number % 10;
if (number == 4 * (tens + ones)) {
if (!first) cout << " ";
cout << number;
first = false;
}
}
return 0;
}
```
**易错提醒:** 枚举顺序已经从小到大,不需要任何排序。
### 扩展4日期的下一天【中等】
**题目描述:** 输入一个日期,输出下一天。需要正确处理月末、年末和闰年二月。
**输入格式:** 输入年份、月份和日期。
**输出格式:** 输出下一天的年份、月份和日期。
**数据范围:** 输入保证是合法日期,年份为 19999。
**输入样例:**
```text
2024 2 29
```
**输出样例:**
```text
2024 3 1
```
**解题思路:** 先根据月份和闰年确定本月天数,日期加一后按顺序处理月进位和年进位。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int year, month, day;
cin >> year >> month >> day;
int days;
if (month == 2) {
bool leap = year % 400 == 0 ||
(year % 4 == 0 && year % 100 != 0);
days = leap ? 29 : 28;
} else if (month == 4 || month == 6 ||
month == 9 || month == 11) {
days = 30;
} else {
days = 31;
}
day++;
if (day > days) {
day = 1;
month++;
if (month > 12) {
month = 1;
year++;
}
}
cout << year << " " << month << " " << day;
return 0;
}
```
**易错提醒:** 必须先处理日期进位,再处理月份和年份进位。
### 扩展5比赛积分模拟【中等】
**题目描述:** 一支球队进行若干场比赛,胜得 3 分、平得 1 分、负得 0 分。输入每场进球数和失球数,输出总积分及胜、平、负场次。
**输入格式:** 第一行输入比赛场数;随后每行输入一场比赛的进球数和失球数。
**输出格式:** 依次输出总积分、胜场、平场和负场。
**数据范围:** 比赛场数为 1100进球数和失球数为非负整数。
**输入样例:**
```text
3
2 1
0 0
1 3
```
**输出样例:**
```text
4 1 1 1
```
**解题思路:** 按场次模拟,根据比分三分支更新积分和统计量。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, points = 0, win = 0, draw = 0, lose = 0;
cin >> n;
for (int i = 0; i < n; i++) {
int scored, conceded;
cin >> scored >> conceded;
if (scored > conceded) {
points += 3;
win++;
} else if (scored == conceded) {
points++;
draw++;
} else {
lose++;
}
}
cout << points << " " << win << " " << draw << " " << lose;
return 0;
}
```
**易错提醒:** 三种比赛结果互斥,使用多层分支。
### 扩展6约瑟夫报数简化版【略难】
**题目描述:** 若干名同学围成一圈,从 1 号开始依次报数,报到指定数字的人离开,继续从下一人报数,直到只剩一人。输出最后留下的编号。
**输入格式:** 输入同学人数和离开所对应的报数。
**输出格式:** 输出最后留下的同学编号。
**数据范围:** 同学人数为 1100报数为 1100。
**输入样例:**
```text
5 3
```
**输出样例:**
```text
4
```
**解题思路:** 用数组记录是否仍在圈中,循环移动当前位置,只对未离开的人计数,报到指定数字时标记离开。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, limit;
bool alive[101] = {false};
cin >> n >> limit;
for (int i = 1; i <= n; i++) alive[i] = true;
int remaining = n, position = 0, count = 0;
while (remaining > 1) {
position = position % n + 1;
if (!alive[position]) continue;
count++;
if (count == limit) {
alive[position] = false;
remaining--;
count = 0;
}
}
for (int i = 1; i <= n; i++)
if (alive[i]) cout << i;
return 0;
}
```
**易错提醒:** 离开的人不再参与报数;位置到末尾后要回到 1 号。
### 扩展7自动售货机【略难】
**题目描述:** 售货机初始余额为 0。依次处理若干操作正数表示投入金额-1 表示购买 3 元商品;-2 表示购买 5 元商品。余额足够才扣款并增加购买次数,否则记录一次失败。输出最终余额、成功购买次数和失败次数。
**输入格式:** 第一行输入操作数量;第二行输入所有操作代码。
**输出格式:** 依次输出最终余额、成功购买次数和失败次数。
**数据范围:** 操作数量为 11000投入金额为 1100。
**输入样例:**
```text
7
5 -1 -2 8 -2 -1 1
```
**输出样例:**
```text
3 3 1
```
**解题思路:** 按输入顺序模拟。投入金额直接增加余额;购买操作先确定价格,再判断余额是否足够。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n, balance = 0, success = 0, failure = 0;
cin >> n;
for (int i = 0; i < n; i++) {
int operation;
cin >> operation;
if (operation > 0) {
balance += operation;
} else {
int price = (operation == -1) ? 3 : 5;
if (balance >= price) {
balance -= price;
success++;
} else {
failure++;
}
}
}
cout << balance << " " << success << " " << failure;
return 0;
}
```
**易错提醒:** 失败购买不能扣除余额;操作必须严格按输入顺序处理。

View File

@@ -0,0 +1,182 @@
# 第11课综合练习
## 教学目标
- 混合题型训练,覆盖一二级核心知识点
- 查漏补缺,找到薄弱环节
- 提高解题速度和准确率
## 核心考点:混合题的拆解与算法组合
### 考什么
从综合题中识别多个________为它们选择已经学过的代码模板并正确组合。
### 必须理解
先把题目拆成“________→________→________”。看到“每个”常想到________看到“最大/最小”想到________看到“出现次数”想到________看到“如果/否则”想到________。
### 核心写法
```text
1. 圈出输入、输出和________
2. 把处理过程拆成若干________
3. 为每个任务选择________
4. 确定步骤顺序和________变量
5. 用样例与________值检查
```
### 常见错误
- 为什么不能读完题就立刻写代码________________
- 下标、数值和次数混用会造成什么问题____________
- 除了题目样例还应测试哪些情况________________
### 判断是否掌握
- [ ] 我能写出输入—处理—输出结构。
- [ ] 我能把处理过程拆成小任务。
- [ ] 我能说明每一步使用的代码模板。
---
## 题目一:成绩等级判断(分支 + 输入输出)
### 核心代码(一句话)
> 多分支判断的核心:**`______` 链条**,从大到小或从小到大依次判断,每种情况一个分支。
### 自然语言思路
### 伪代码
```
1. 输入 score
2. if score >= 90 输出 ______
3. else if score >= 80 输出 ______
4. else if score >= 70 输出 ______
5. else if score >= 60 输出 ______
6. else 输出 ______
```
### 真实代码
```cpp
```
### 注意点
---
## 题目二:数组求最大值及下标(数组 + 循环 + 打擂台)
### 核心代码(一句话)
> 带下标记录的打擂台:**用一个变量记______另一个变量记______**,每次换擂主时两个一起更新。
### 自然语言思路
### 伪代码
```
1. 输入 n
2. 读入数组 arr[n]
3. maxVal = ______, maxIndex = ______
4. for i = ______ 到 ______
5. if arr[i] ______ maxVal // 严格大于
6. maxVal = ______
7. maxIndex = ______
8. 输出 maxVal 和 maxIndex
```
### 真实代码
```cpp
```
### 注意点
---
## 题目三字符串分类统计string + 字符判断 + 多计数器)
### 核心代码(一句话)
> 字符串分类统计的核心:**______ + 多分支判断 + 多个计数器**,每个 `if` 分支对应一个计数器。
### 自然语言思路
### 伪代码
```
1. ______ 读取字符串 s
2. cnt1 = 0, cnt2 = 0, cnt3 = 0, cnt4 = 0
3. for i = 0 到 ______
4. if ______(s[i]) cnt1++
5. else if ______(s[i]) cnt2++
6. else if ______(s[i]) cnt3++
7. else cnt4++
8. 输出四个计数结果
```
### 真实代码
```cpp
```
### 注意点
---
## 限时练习(课堂完成)
### 练习组一(限时 30 分钟)
1. 输入两个数,输出较大的数
2. 输入 n输出 n 的阶乘
3. 输入一个字符串,判断是否为回文
### 练习组二(限时 30 分钟)
1. 输入 n 个数,求平均值
2. 找出 100 以内的所有素数
3. 输入一个正整数,输出它的逆序数
### 练习组三(限时 30 分钟)
1. 输入年份,判断是否为闰年
2. 输入 n打印左下直角三角形
3. 输入一个字符串,统计其中数字的个数
---
## 解题技巧
### 1. 仔细审题
### 2. 先想后写
### 3. 测试验证
### 4. 时间管理
---
## 常见错误检查清单
- [ ] 头文件是否包含
- [ ] `using namespace std;` 是否写了
- [ ] 变量是否初始化
- [ ] 数组下标是否越界
- [ ] 循环边界是否正确
- [ ] 输出格式是否正确
- [ ] 是否处理了边界情况
- [ ] 整数除法是否需要转浮点数
- [ ] string 输入是否用了 `getline`(含空格时)
---
## 课后作业
> 请完成本课对应的课后作业,见编程题文件。

View File

@@ -0,0 +1,292 @@
# 第11课综合练习
## 教学目标
- 混合题型训练,覆盖一二级核心知识点
- 查漏补缺,找到薄弱环节
- 提高解题速度和准确率
## 核心考点:混合题的拆解与算法组合
### 考什么
综合题不会直接告诉你使用哪一课的知识,而是把输入输出、分支、循环、数组、字符串等组合起来。考试重点是从题意中识别若干小任务,为每个小任务选择已经学过的代码模式,再按正确顺序连接。
### 必须理解
先把题目拆成“输入什么→处理什么→输出什么”三段。处理部分继续拆成动作,例如“遍历数组→筛选满足条件的元素→打擂台求最值→输出下标”。每个动作只解决一个问题,再把前一步结果交给下一步。读到“每个”通常想到遍历,读到“最大/最小”想到打擂台,读到“出现次数”想到计数,读到“如果/否则”想到分支。组合算法时要明确数据在各步骤中的含义,不能边读题边盲写代码。
### 核心写法
```text
1. 圈出输入、输出和数据范围
2. 把处理过程拆成若干小任务
3. 为每个小任务选择代码模板
4. 确定各步骤先后与中间变量
5. 写代码后用样例和边界值检查
```
例如“找数组中出现次数最多的数”可拆成:数组输入 + 频次统计 + 打擂台 + 输出答案。
### 常见错误
- 一看到长题目就直接写代码,没有先拆任务。
- 只完成主算法,漏掉输入格式、并列规则或输出顺序。
- 不区分中间变量的含义,把下标、数值和次数混用。
- 只测试题目样例,不测试空边界、最小值、最大值或并列情况。
### 判断是否掌握
拿到一道混合题,能在动手前写出清晰的三到五步伪代码,并能指出每一步使用了哪种基础结构或算法模板。
---
## 题目一:成绩等级判断(分支 + 输入输出)
### 核心代码(一句话)
> 多分支判断的核心:**`if - else if - else` 链条**,从大到小或从小到大依次判断,每种情况一个分支。
### 自然语言思路
输入一个 0-100 的成绩输出对应的等级90-100 → A80-89 → B70-79 → C60-69 → D60以下 → E。
第一步,接收输入 `score`
第二步,从高分往低分判断:`if (score >= 90)` → A`else if (score >= 80)` → B依次类推。
第三步,最后一个 `else` 兜底,对应 E 等级。
第四步,注意判断顺序:如果从小到大判断(先 `score < 60`),要看清楚边界别重叠。
> GESP 考试中这类题会以"程序阅读"或"编程题"形式出现,要注意 if-else 的匹配逻辑。
### 伪代码
```
1. 输入 score
2. if score >= 90 输出 "A"
3. else if score >= 80 输出 "B"
4. else if score >= 70 输出 "C"
5. else if score >= 60 输出 "D"
6. else 输出 "E"
```
### 真实代码
```cpp
#include <iostream>
using namespace std;
int main() {
int score;
cin >> score;
if (score >= 90) {
cout << "A" << endl;
} else if (score >= 80) {
cout << "B" << endl;
} else if (score >= 70) {
cout << "C" << endl;
} else if (score >= 60) {
cout << "D" << endl;
} else {
cout << "E" << endl;
}
return 0;
}
```
### 注意点
1. **判断顺序很重要**:从大到小写最清晰,因为 `else if` 意味着"前面条件都不满足"。如果从小到大写,要注意边界。
2. **边界值**90 是 A89 是 B。如果写成 `score > 90` 就会把 90 分的人划到 B检查时要留意。
3. **输入范围**:题目保证输入在 0-100但实际考试中如果输入 101 按上面代码会输出 "A"(因为 ≥90。严谨的做法是加范围检查。
---
## 题目二:数组求最大值及下标(数组 + 循环 + 打擂台)
### 核心代码(一句话)
> 带下标记录的打擂台:**用一个变量记最大值,另一个变量记下标**,每次换擂主时两个一起更新。
### 自然语言思路
输入 n 个数,输出最大值和它第一次出现的下标。
第一步,读入数组。
第二步,设 `maxVal = arr[0]``maxIndex = 0`,两个变量一起初始化为第一个元素的值和下标。
第三步,从 `i = 1` 开始遍历,如果 `arr[i] > maxVal`(严格大于),则同时更新 `maxVal = arr[i]``maxIndex = i`
第四步,遍历结束,输出最大值和下标。
> 注意是"严格大于" `>`,不是 `>=`。如果写成 `>=`,遇到重复最大值时会记录最后一次出现的位置。
### 伪代码
```
1. 输入 n
2. 读入数组 arr[n]
3. maxVal = arr[0], maxIndex = 0
4. for i = 1 到 n-1
5. if arr[i] > maxVal // 严格大于
6. maxVal = arr[i]
7. maxIndex = i
8. 输出 maxVal 和 maxIndex
```
### 真实代码
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int arr[n];
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
int maxVal = arr[0];
int maxIndex = 0;
for (int i = 1; i < n; i++) {
if (arr[i] > maxVal) {
maxVal = arr[i];
maxIndex = i;
}
}
cout << "最大值:" << maxVal << ",下标:" << maxIndex << endl;
return 0;
}
```
### 注意点
1. **`>` vs `>=`**:用 `>` 保留第一次出现的下标,用 `>=` 保留最后一次出现的下标。看题目要求。
2. **maxIndex 初始值**:必须和 maxVal 的初始值对应。如果 maxVal 从 arr[0] 开始maxIndex 就是 0。
3. **循环从 1 开始**:因为 arr[0] 已经是初始擂主了。
---
## 题目三字符串分类统计string + 字符判断 + 多计数器)
### 核心代码(一句话)
> 字符串分类统计的核心:**遍历 + 多分支判断 + 多个计数器**,每个 `if` 分支对应一个计数器。
### 自然语言思路
输入一行字符串,统计其中大写字母、小写字母、数字、其他字符(含空格)各有多少个。
第一步,用 `getline(cin, s)` 读入整行(因为可能含空格)。
第二步,设四个计数器 `upper, lower, digit, other` 都初始为 0。
第三步,遍历每个字符 `s[i]`,用 `isupper``islower``isdigit` 判断类型,对应计数器 +1。
第四步,都不满足的归入 `other`
第五步,输出四个统计结果。
第六步,注意空格也算"其他",如果不想算空格可以先单独判断 `s[i] == ' '`
### 伪代码
```
1. getline 读取字符串 s
2. upper = 0, lower = 0, digit = 0, other = 0
3. for i = 0 到 s.length()-1
4. if isupper(s[i]) upper++
5. else if islower(s[i]) lower++
6. else if isdigit(s[i]) digit++
7. else other++
8. 输出四个计数结果
```
### 真实代码
```cpp
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main() {
string s;
getline(cin, s);
int upper = 0, lower = 0, digit = 0, other = 0;
for (int i = 0; i < s.length(); i++) {
if (isupper(s[i])) {
upper++;
} else if (islower(s[i])) {
lower++;
} else if (isdigit(s[i])) {
digit++;
} else {
other++;
}
}
cout << "大写:" << upper
<< ",小写:" << lower
<< ",数字:" << digit
<< ",其他:" << other << endl;
return 0;
}
```
### 注意点
1. **`getline` 不是 `cin`**`cin >> s` 遇空格就停,"Hello World" 只会读到 "Hello"。
2. **`isupper` 等函数需要 `<cctype>`**:别忘了加这个头文件。
3. **else 分支的归属**:空格、标点符号都会进 `other`。如果题目要求单独统计空格,需要在 `else` 前加 `else if (s[i] == ' ')`
4. **空的字符串**:如果什么都没输入,四个计数器都是 0输出全零不会出错。
---
## 限时练习(课堂完成)
### 练习组一(限时 30 分钟)
1. 输入两个数,输出较大的数(基础输入输出 + if
2. 输入 n输出 n 的阶乘 `1 × 2 × 3 × ... × n`(循环 + 累乘)
3. 输入一个字符串判断是否为回文string + 循环)
### 练习组二(限时 30 分钟)
1. 输入 n 个数,求平均值(数组 + 求和 + 浮点数)
2. 找出 100 以内的所有素数(循环 + 判断素数)
3. 输入一个正整数,输出它的逆序数(如 12345 → 54321数位拆解
### 练习组三(限时 30 分钟)
1. 输入年份,判断是否为闰年(能被 4 整除且不能被 100 整除,或能被 400 整除)
2. 输入 n打印左下直角三角形嵌套循环
3. 输入一个字符串统计其中数字的个数string + isdigit
---
## 解题技巧
### 1. 仔细审题
- 看清输入输出格式(空格、换行、大小写)
- 注意数据范围(决定用 `int` 还是 `long long`
- 理解题目要求(从小到大输出还是从大到小)
### 2. 先想后写
- 在草稿纸上写出核心步骤
- 用伪代码搭好骨架
- 再动手写真实代码
### 3. 测试验证
- 先用样例数据测试
- 再试边界情况0、1、最大值、最小值
- 检查输出格式是否完全一致
### 4. 时间管理
- 先做简单的题,拿稳基础分
- 不会的题先跳过,不要死磕
- 留 10 分钟检查
---
## 常见错误检查清单
在交卷前逐条检查:
- [ ] 头文件是否包含(`<iostream>``<string>``<cmath>``<cctype>` 等)
- [ ] `using namespace std;` 是否写了
- [ ] 变量是否初始化(特别是累加器、计数器)
- [ ] 数组下标是否越界(从 0 到 n-1
- [ ] 循环边界是否正确(`<` vs `<=`
- [ ] 输出格式是否正确(空格、换行、中文/英文标点)
- [ ] 是否处理了边界情况0、1、负数等
- [ ] 整数除法是否需要转浮点数
- [ ] string 输入是否用了 `getline`(含空格时)
---
## 本课打油诗
> 综合练习查漏洞,各种题型过一遍。
> 先审题意后动手,伪代码里思路现。
> 边界条件要想全,样例测完再交卷。
> 时间分配有策略,不会先跳回头见。
---
## 课后作业
1. 补完课堂限时练习中未完成的题目
2. 整理自己的错题本,写出错误原因和正确解法
3. 在洛谷上完成 GESP 一级真题中的 3 道编程题