Files
project1/第09课-枚举模拟/教案-教师版.md

228 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第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³