本次课 3 小时:前 45 分钟复盘 10/10 的整卷①,然后是本模块。
| 模块 | 内容 | 复用的旧讲义 | 本讲义新写 |
|---|---|---|---|
| M11 动态规划一 | DP 三件套、数字三角形、01 背包、最大子段和、最长上升子序列、完全背包、背包计数 | L05 全部、L04 第 5 节 |
两种状态设计、最长上升子序列、完全背包、方案数、恰好装满 |
带着 S9 的复盘表来。老师只问三件事:
freopen、格式丢分的?01 背包:输入背包容量 T 和物品数 m(T ≤ 1000,m ≤ 100),接下来 m 行每行一个物品的重量和价值。每个物品最多拿一次,求总重量不超过 T 时的最大总价值。用一维数组写。
输入
70 3/71 100/69 1/1 2→ 输出3
#include <bits/stdc++.h>
using namespace std;
int f[1005]; // f[j]:容量为 j 时的最大价值(全局,自动为 0)
int main() {
int T, m;
cin >> T >> m;
for (int i = 1; i <= m; i++) {
int w, v;
cin >> w >> v;
for (int j = T; j >= w; j--) // 01 背包:倒序
f[j] = max(f[j], f[j - w] + v);
}
cout << f[T] << endl;
return 0;
}写对了 → 看 1.2
起。写不出,或者内层写成了正序 → 先读 L05
采药那一节(二维怎么压成一维、为什么倒序)。
| 内容 | 在哪 |
|---|---|
| DP 三件套:状态定义、转移方程、初始状态 | L05 开头 |
| 数字三角形(为什么不能贪心) | L05 例题一 |
| 01 背包:二维 → 一维、内层倒序、正序会发生什么 | L05 例题二 |
| 最大子段和 | L04 第 5 节 |
拿到一道序列上的 DP 题,状态几乎总是下面两种之一:
| 设计 | f[i] 的意思 | 答案在哪 | 例子 |
|---|---|---|---|
| 以 i 结尾 | 以第 i 个元素结尾的最优值 | max(f[1..n]),要扫一遍 |
最大子段和、最长上升子序列 |
| 前 i 个 | 只考虑前 i 个元素时的最优值 | f[n] |
背包(前 i 个物品)、走楼梯 |
⚠️ "以 i 结尾"的答案不是
f[n],因为最优的那一段不一定以最后一个元素结尾。这是这类题最常见的错。
从序列里挑出一些数(不必连续,但顺序不变),使它们严格递增,最长能挑几个?
三件套:
#include <bits/stdc++.h>
using namespace std;
int a[5005], f[5005];
int main() {
int n;
cin >> n;
// n<=5000,O(n^2)=2.5e7,能过
for (int i = 1; i <= n; i++) cin >> a[i];
int ans = 0;
for (int i = 1; i <= n; i++) {
f[i] = 1; // 初始:自己一个
for (int j = 1; j < i; j++)
if (a[j] < a[i]) f[i] = max(f[i], f[j] + 1);
ans = max(ans, f[i]); // 答案是所有 f[i] 的最大值
}
cout << ans << endl;
return 0;
}📝
变形只改一个符号:最长不下降(可以相等)把
< 改成 <=;最长下降改成
>。读题时圈出"严格"两个字。
⚠️ n 到 10⁵ 时 O(n²) 就超时了,要 O(n log n) 的做法(二分),那属于提高内容。n ≤ 10⁵ 的 LIS 题,O(n²) 能拿 n ≤ 5000 那几档部分分——K01 讲的"数据范围告诉你暴力能拿几分"。
每种物品可以拿无限次。 和 01 背包的代码只差一个循环方向:
#include <bits/stdc++.h>
using namespace std;
long long f[100005];
int main() {
int T, m;
cin >> T >> m;
for (int i = 1; i <= m; i++) {
int w, v;
cin >> w >> v;
for (int j = w; j <= T; j++) // 完全背包:正序
f[j] = max(f[j], f[j - w] + v);
}
cout << f[T] << endl;
return 0;
}📝 为什么正序就是"可以拿无限次":正序时,算
f[j] 用到的 f[j - w]
已经是这一轮更新过的——它里面可能已经放了一个物品
i,再加一个就是放了两个。倒序时 f[j - w]
还是上一轮的,所以物品 i 最多放一次。
同一组数据,01 背包答案 3,完全背包答案 140(拿 70
个重量 1、价值 2 的物品)。L05 说"正序会输出
140",就是这个。
⚠️ 价值总和可能很大,f 用
long long。课堂题 P1616 疯狂的采药不开
long long 会错。
| 问法 | 初值 | 转移 |
|---|---|---|
| 不超过 T 的最大价值 | 全部 0 | f[j] = max(f[j], f[j-w] + v) |
| 恰好装满 T 的最大价值 | f[0] = 0,其余全是负无穷 |
同上 |
| 恰好装满 T 的方案数 | f[0] = 1,其余 0 |
f[j] += f[j-w] |
为什么"恰好装满"要把初值设成负无穷:f[j] 初值 0 等于在说"容量 j 什么都不放也行"。恰好装满时这不合法,用负无穷表示"这个状态不存在",它加上什么都还是很小,不会被选中。
#include <bits/stdc++.h>
using namespace std;
int f[10005];
int main() {
// 方案数:用若干个数(每个最多用一次)凑出恰好 M 的方法数
int n, M;
cin >> n >> M;
f[0] = 1; // 凑出 0:什么都不选,1 种
for (int i = 1; i <= n; i++) {
int a;
cin >> a;
for (int j = M; j >= a; j--) f[j] += f[j - a]; // 每个数只能用一次:倒序
}
cout << f[M] << endl;
return 0;
}📝 这就是作业 P1164 小 A 点菜。
f[n]。long long。| 题目 | 要点 |
|---|---|
| P1616 疯狂的采药(橙) | 完全背包 + long long;t 到
10⁷,数组开多大、内存够不够,自己算(规矩 1) |
| B3637 最长上升子序列(橙) | 就是 1.3 节。合上讲义重写一遍再交,不许抄 |
| P1049 装箱问题(橙) | 01 背包:价值就等于体积 |
| 档 | 5 道(按顺序做) |
|---|---|
| B 档 | ① P1048 采药(橙,L05 讲过但从没交过) ② P1060
开心的金明(橙,01 背包) ③ P1164 小 A 点菜(橙,1.5 节方案数) ④ P1216
数字三角形(橙,L05 例题) ⑤ P1091 合唱队形(橙,LIS
正着一遍反着一遍) |
| A 档 | ① P1060 开心的金明(橙) ② P1164 小 A 点菜(橙) ③ P1091 合唱队形(橙) ④ P2196 挖地雷(黄,"以 i 结尾"+ 输出路径) ⑤ P1095 守望者的逃离(黄) |
10/17(六)08:30–12:00 整卷② NOIP2016 普及组,规矩同
F0 第四节。
f[0] 为什么是 1?本次打卡:M11 ①(盲写);作业 AC 后打 ②③。