更新各班级出勤登记与课评汇总,新增教学日程查询技能与CSP03枚举算法教案

This commit is contained in:
chengzi
2026-05-05 18:53:11 +08:00
parent 1f276f874b
commit b7cd74392c
447 changed files with 57291 additions and 148 deletions

View File

@@ -0,0 +1,580 @@
# CSP03-08 枚举算法
## 一、课程简介5分钟
### 🎯 课程目标
1. 理解算法的三种描述方式:自然语言、流程图、伪代码
2. 掌握枚举法(穷举法)的核心思想
3. 理解枚举法的边界设计原则
4. 能够用枚举法解决常见的穷举搜索类问题
### 📚 核心知识点
- 算法的描述方式:自然语言(文字说明)、流程图(图形表示)、伪代码(接近代码的文字)
- 枚举法的定义:逐一列举所有可能的解,逐个验证
- 枚举的范围(边界):如何确定起点和终点
- 枚举的验证条件:什么样的值满足要求
- 枚举的优化:剪枝、缩小枚举范围
- 枚举法的时间复杂度分析
---
## 二、知识回顾10分钟
### 👩‍🏫 教师引导
> 同学们,我们在 CSP03 前几节学了数组和字符串。从这节课开始,我们进入**算法**的世界!
>
> 什么是算法?就是解决问题的**方法和步骤**。
>
> 今天先从最简单的算法思想开始——**枚举法**,也叫穷举法:用最"笨"的方法,把所有可能的答案都试一遍!
>
> 虽然"笨",但是有效!很多竞赛题用枚举就能解决!
**互动复习:**
> - for 循环可以控制变量从 a 到 b 遍历,这不就是在"枚举" a 到 b 的所有值吗?
> - 学了枚举法,就相当于给 for 循环赋予了"算法内涵"
---
## 三、新知讲解45分钟
### 1. 新知导入 🎬
> **猜数游戏:** 我脑子里想了一个 1~100 之间的整数,你能猜出来吗?
>
> 最"笨"的方法:从 1 猜到 100总能猜到——这就是枚举法
>
> 实际上,枚举法是很多算法的基础。很多看似复杂的问题,暴力枚举都能解决——只要数据规模不太大。
>
> 信奥比赛中,枚举法写得好,可以得到部分分甚至满分!
---
### 2. 知识点讲解
#### 2.1 算法的三种描述方式
**① 自然语言**(文字描述):
> 找出 1~100 中所有能被 3 整除的数:
> 从 1 遍历到 100对每个数判断能否被 3 整除,若能则输出。
**② 流程图**(图形表示关键节点和流向):
```
开始
i = 1
i <= 100? → 否 → 结束
↓ 是
i % 3 == 0? → 否 → i++
↓ 是
输出 i
i++
(返回判断)
```
**③ 伪代码**(介于自然语言和代码之间):
```
FOR i FROM 1 TO 100:
IF i MOD 3 == 0:
OUTPUT i
```
**代码实现:**
```cpp
for (int i = 1; i <= 100; i++) {
if (i % 3 == 0) cout << i << " ";
}
```
---
#### 2.2 枚举法的核心思想
**枚举法 = 确定范围 + 逐一验证**
**三要素:**
1. **枚举的对象**:什么是我们要找的"候选答案"
2. **枚举的范围(边界)**:候选答案的取值范围?
3. **验证的条件**:候选答案满足什么条件才算正确答案?
---
#### 2.3 枚举的边界设计
**原则:宁可多枚举,不要漏掉正确答案!**
但也要避免枚举太多导致超时。
**示例:百钱百鸡问题**
100 文钱买 100 只鸡,鸡翁 5 文/只,鸡母 3 文/只,鸡雏 1 文/3只三种都要买各买几只
**枚举分析:**
- 设公鸡 x 只范围0 到 20因为 5×20=100
- 设母鸡 y 只范围0 到 33因为 3×33=99<100
- 小鸡 z = 100 - x - y
**验证条件:**
- `z >= 0`(鸡的数量不为负)
- `z % 3 == 0`(小鸡必须是 3 的倍数)
- `5*x + 3*y + z/3 == 100`(总价格等于 100
```cpp
for (int x = 0; x <= 20; x++) {
for (int y = 0; y <= 33; y++) {
int z = 100 - x - y;
if (z >= 0 && z % 3 == 0 && 5*x + 3*y + z/3 == 100) {
cout << "公鸡:" << x << " 母鸡:" << y << " 小鸡:" << z << endl;
}
}
}
```
> 📌 黑板推演:两层循环,每次组合验证一种可能。虽然最多循环 21×34=714 次,但逻辑清晰,代码简洁。
---
#### 2.4 枚举法的时间复杂度
| 枚举层数 | 时间复杂度 | 适用规模n |
|---------|-----------|------------|
| 单层枚举 | O(n) | n ≤ 10⁸ |
| 双层枚举 | O(n²) | n ≤ 10⁴ |
| 三层枚举 | O(n³) | n ≤ 500 |
> ⚠️ 竞赛中,一般 10⁸ 次操作是 1 秒的上限,超过则可能超时!
---
#### 2.5 枚举法的优化(剪枝)
**剪枝**:在枚举过程中,提前跳过不可能是答案的情况。
**示例:** 找满足 a + b + c = 100 的正整数组合a≤b≤c
```cpp
// 优化前三层循环O(n³)
for (int a = 1; a <= 100; a++)
for (int b = 1; b <= 100; b++)
for (int c = 1; c <= 100; c++)
if (a + b + c == 100) ...
// 优化后剪枝c = 100 - a - b直接计算
for (int a = 1; a <= 100; a++)
for (int b = a; b <= 100; b++) { // b >= a
int c = 100 - a - b;
if (c >= b) ... // 满足 c >= b 才输出
}
```
**效率对比:** 三层循环 100³ = 10⁶ 次;优化后约 10⁴/2 次!
---
### 3. GESP 真题演练 ⚡
**抢答题 1选择题**
> 使用枚举法求 1 到 1000 中所有完全平方数(即某个整数的平方),最合理的枚举方式是(
>
> A. 枚举 1~1000 的所有数,判断其是否是某整数的平方
> B. 枚举 1~31 的所有整数 i计算 i² 加入结果
> C. 枚举 1~1000 的所有数对 (i, j),判断 i×j 是否满足
> D. 随机猜测
>
> **答案B**
> 解析√1000 ≈ 31.6,所以枚举 i 从 1 到 31计算 i² 即可找到 1~1000 内所有完全平方数,效率最高。
**判断题 2**
> 枚举法的核心思想是:逐一检验所有候选答案,找出满足条件的解。(
>
> **答案:✓(正确)**
**抢答题 3**
> 以下哪种问题**不适合**用枚举法解决?
>
> A. 在 1~100 中找满足某条件的整数
> B. 在 n=10⁹ 的数组中找出所有满足条件的下标对
> C. 在一个 4×4 的棋盘上找所有放置方式
> D. 在 1~1000 中找所有质数
>
> **答案B**
> 解析n=10⁹ 时,二层枚举需要 10¹⁸ 次,严重超时,需要更高效的算法。
---
### 4. 进阶扩展
**枚举法的经典模板:**
```cpp
// 模板1单变量枚举
for (int x = ; x <= ; x++) {
if ((x)) {
;
}
}
// 模板2双变量枚举
for (int x = 1; x <= 1; x++) {
for (int y = 2; y <= 2; y++) {
if ((x, y)) {
;
}
}
}
```
**枚举法的应用领域:**
- 数字问题(整数分解、质因数)
- 组合问题(选 k 个数的方案)
- 棋盘问题(八皇后、数独)
- 密码暴力破解(有限字符集)
---
## 四、课堂练习45分钟🎈
### 练习 1基础找因数
**题目描述:**
输入一个正整数 n输出 n 的所有因数(包括 1 和 n 本身),从小到大排列。
**输入格式:**
- 一个正整数 n1 ≤ n ≤ 10000
**输出格式:**
- 所有因数,空格分隔
**样例输入:**
```
12
```
**样例输出:**
```
1 2 3 4 6 12
```
**题解代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
int n; cin >> n;
for (int i = 1; i <= n; i++) {
if (n % i == 0) cout << i << " ";
}
cout << endl;
return 0;
}
```
---
### 练习 2基础判断质数
**题目描述:**
输入一个正整数 n判断它是否是质数素数
**输入格式:**
- 一个正整数 n2 ≤ n ≤ 10000
**输出格式:**
- `YES``NO`
**样例输入:**
```
17
```
**样例输出:**
```
YES
```
**题解代码:**
```cpp
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n; cin >> n;
bool isPrime = true;
for (int i = 2; i <= sqrt(n); i++) {
if (n % i == 0) { isPrime = false; break; }
}
cout << (isPrime ? "YES" : "NO") << endl;
return 0;
}
```
---
### 练习 3综合百钱百鸡
**题目描述:**
100 文钱买 100 只鸡:公鸡 5 文/只,母鸡 3 文/只,小鸡 1 文/3只每种至少一只找出所有购买方案。
**输入格式:**(无输入)
**输出格式:**
- 每行输出一种方案:`公鸡x只 母鸡y只 小鸡z只`
**样例输出:**
```
公鸡4只 母鸡18只 小鸡78只
公鸡8只 母鸡11只 小鸡81只
公鸡12只 母鸡4只 小鸡84只
```
**题解代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
for (int x = 1; x <= 19; x++) {
for (int y = 1; y <= 32; y++) {
int z = 100 - x - y;
if (z > 0 && z % 3 == 0 && 5*x + 3*y + z/3 == 100) {
cout << "公鸡" << x << "只 母鸡" << y << "只 小鸡" << z << "" << endl;
}
}
}
return 0;
}
```
---
### 练习 4综合找出所有三位水仙花数
**题目描述:**
水仙花数是这样的三位数:每一位数字的立方之和等于本身(如 153 = 1³+5³+3³。输出所有水仙花数。
**输入格式:**(无)
**输出格式:**
- 每行一个水仙花数
**样例输出:**(部分)
```
153
370
371
407
```
**题解代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
for (int n = 100; n <= 999; n++) {
int a = n / 100; // 百位
int b = n / 10 % 10; // 十位
int c = n % 10; // 个位
if (a*a*a + b*b*b + c*c*c == n) {
cout << n << endl;
}
}
return 0;
}
```
---
### 练习 5进阶两数之和
**题目描述:**
输入 n 个不同整数和目标值 target从数组中找出两个数使它们的和等于 target输出这两个数先小后大。保证有且只有一组解。
**输入格式:**
- 第一行 n 和 target
- 第二行 n 个整数
**输出格式:**
- 两个整数,空格分隔
**样例输入:**
```
5 9
2 7 11 15 4
```
**样例输出:**
```
2 7
```
**题解代码:**
```cpp
#include <iostream>
using namespace std;
int a[1005];
int main() {
int n, target;
cin >> n >> target;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (a[i] + a[j] == target) {
int x = min(a[i], a[j]);
int y = max(a[i], a[j]);
cout << x << " " << y << endl;
return 0;
}
}
}
return 0;
}
```
---
## 五、课堂总结5分钟🌟
> 今天我们学习了算法世界的入门——**枚举法**
>
> 枚举法三要素:
> 1. **枚举对象**:谁是候选答案?
> 2. **枚举范围(边界)**:从哪里到哪里?
> 3. **验证条件**:判断当前候选是否正确
>
> 算法描述方式:自然语言、流程图、伪代码——三种说同一件事,理解即可。
>
> 下节课我们学"模拟算法"——让计算机按照现实逻辑一步步执行,解决更复杂的问题!
---
## 六、课后作业与拓展10分钟
### 📝 课后作业3道
#### 作业 1输出 n 以内所有质数
输入正整数 n输出 2 到 n 之间所有质数,每行一个。
**样例:** n=10 → 2 3 5 7
```cpp
#include <iostream>
#include <cmath>
using namespace std;
bool isPrime(int x) {
if (x < 2) return false;
for (int i = 2; i <= sqrt(x); i++) if (x%i==0) return false;
return true;
}
int main() {
int n; cin>>n;
for (int i=2;i<=n;i++) if(isPrime(i)) cout<<i<<endl;
return 0;
}
```
---
#### 作业 2找完全数
正整数中,"完全数"是所有真因子(不包含本身)之和等于自身的数(如 6 = 1+2+3。输出 1~1000 内的所有完全数。
**样例输出:** 6 28 496
```cpp
#include <iostream>
using namespace std;
int main() {
for (int n=1;n<=1000;n++) {
int sum=0;
for (int i=1;i<n;i++) if(n%i==0) sum+=i;
if (sum==n) cout<<n<<endl;
}
return 0;
}
```
---
#### 作业 3找满足条件的三位数
找出所有三位整数 n满足n 是 3 的倍数,且 n 的各位数字之积也是 3 的倍数。
**样例输出(部分):** 111 123 ...
```cpp
#include <iostream>
using namespace std;
int main() {
for (int n=100;n<=999;n++) {
int a=n/100, b=n/10%10, c=n%10;
if (n%3==0 && (a*b*c)%3==0) cout<<n<<endl;
}
return 0;
}
```
---
### 🔥 拓展习题尖子生挑战7道
#### 挑战 1四平方和定理
任意正整数都可以表示为最多4个完全平方数之和。对于给定的 n找出使用最少个完全平方数表示 n 的方案最少2个输出具体方案中数量最少的一种
**提示:** 枚举所有 ≤ √n 的完全平方数组合。
#### 挑战 2火柴棍数字
用 n 根火柴棍可以摆出多少种不同的数字0-91种数字可以由多根火柴棍组成请枚举找出可以用恰好 n 根火柴摆出的最大数。火柴数0→6,1→2,2→5,3→5,4→4,5→5,6→6,7→3,8→7,9→6
**提示:** 动态规划或枚举,但对于简单情况可用枚举。
#### 挑战 3扑克牌 24 点
给定 4 张牌1~13判断能否通过加减乘除允许括号得到 24。
**提示:** 枚举 4 个数的所有排列4! = 24 种和运算符的所有组合4³ = 64 种以及括号方式5种共约 7680 种情况。
#### 挑战 4最小覆盖矩形
给定平面上 n 个点,找出面积最小的轴对齐矩形,能包含所有点(枚举法:找 x 和 y 方向上的最小最大值)。
**提示:** 找 min_x, max_x, min_y, max_y面积即 (max_x-min_x)*(max_y-min_y)。
#### 挑战 5质因数分解
输入正整数 n输出其质因数分解结果如 12 = 2² × 3
**提示:** 从 2 开始枚举,能整除就统计个数并除去,直到 n == 1 或当前枚举数的平方大于 n。
#### 挑战 6最小公倍数与最大公因数
枚举法求两个整数 a、b 的最大公因数GCD枚举 1~min(a,b),找最大的能同时整除两者的数。
**提示:** 理解辗转相除法Euclid算法其实更优但枚举在数值小时完全可行。
#### 挑战 7找所有满足条件的数组
给定 n1≤n≤10找出所有满足长度为 n、元素均为 1 到 9、所有元素之积等于给定值 M 的严格递增序列。
**提示:** 递归枚举,每次选一个比上一个大的数,在乘积不超过 M 的情况下继续。