
GESP 六级明日冲刺:最后一晚只抓两个大方向
一、第一重点:树形 DP
为什么我认为树形 DP 必须重点准备?
因为 6 月份六级的第二道编程题就是树形 DP:
《满二叉树》
题目给出一棵有根二叉树,要求统计所有子树中有多少棵是满二叉树。
参考程序非常典型:
void dfs(int u) {
if (!u)
return;
dfs(l[u]);
dfs(r[u]);
h[u] = max(h[l[u]], h[r[u]]) + 1;
chk[u] = chk[l[u]]
&& chk[r[u]]
&& (h[l[u]] == h[r[u]]);
ans += chk[u];
}
这里实际上就是一个非常标准的:
DFS + 子树信息 + DP
的树形 DP。
二、学生今晚必须重温一下树形 DP
不要再花时间背很多奇怪的树 DP。
只要把下面这个思想吃透:
"先问孩子,再算自己。"
例如:
1
/ \
2 3
/ \
4 5
计算节点 1 时,我们必须先知道:
2号节点的情况
3号节点的情况
所以:
dfs(2);
dfs(3);
// 根据 2、3 的结果计算 1
这就是树形 DP 最核心的思想:
DFS 负责从下往上走,DP 负责保存每棵子树的信息。
三、明天树 DP 如果变形,学生要会认
考试不一定再问"满二叉树"。
可能换成:
-
求每棵子树的节点数量
-
求每棵子树的高度
-
求每棵子树的最大值
-
求每棵子树的和
-
判断某个性质
-
统计满足条件的子树
-
求树上的最大路径
-
求选择若干节点的最大价值
-
"选父节点就不能选儿子"之类的问题
但是核心套路仍然可能是:
void dfs(int u)
{
dfs(left);
dfs(right);
// 利用孩子的信息
// 计算 u 的信息
}
给学生一句口诀:
树形 DP:孩子先算,父亲后算;
DFS 往下走,DP 往上收。
四、第二重点:背包 DP
这一部分我认为明天一定要复习到"看到题目就能判断 0/1 还是完全背包"。
因为 6 月份六级选择题第 15 题直接考了:
0/1 背包的一维动态规划
代码中的关键就是:
for (int i = 0; i < n; ++i)
{
for (int w = W; w >= wt[i]; --w)
{
dp[w] = max(dp[w],
dp[w - wt[i]] + val[i]);
}
}
而6 月份第一道编程题《条形蛋糕》本身就是完全背包思想。
五、6 月份编程题 1:《条形蛋糕》
这是明天学生必须重新看一遍的题。
题意是:
有一条长度为 n 的蛋糕。
长度为 i 的蛋糕块售价为 p[i]。
可以把蛋糕切成若干段,要求:
所有蛋糕块长度都是整数,求最大销售价格。
例如 6 月份样例中:
n = 4
价格:
1 5 8 9
程序:
int dp[1010], p[1010];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> p[i];
for (int i = 1; i <= n; ++i)
{
for (int j = i; j <= n; ++j)
{
dp[j] = max(dp[j],
dp[j - i] + p[i]);
}
}
cout << dp[n] << endl;
return 0;
}
六、为什么《条形蛋糕》是完全背包?
这个问题明天非常可能帮助学生判断题型。
比如:
长度 1 → 价格 1
长度 2 → 价格 5
长度 3 → 价格 8
长度 4 → 价格 9
一条长度 4 的蛋糕可以:
4
也可以:
1 + 3
或者:
2 + 2
甚至:
1 + 1 + 1 + 1
注意:
长度 1 的蛋糕块可以使用很多次。
所以它不是 0/1 背包,而是:
完全背包。
七、明天看到背包题,先问自己一个问题
"一个物品能用几次?"
这是最重要的。
| 类型 | 使用次数 | 一维 DP 循环 |
|---|---|---|
| 0/1 背包 | 每种最多 1 次 | w-- |
| 完全背包 | 每种可以很多次 | w++ |
0/1 背包
for (int i = 0; i < n; i++)
{
for (int w = W; w >= wt[i]; w--)
{
dp[w] = max(dp[w],
dp[w-wt[i]] + val[i]);
}
}
完全背包
for (int i = 0; i < n; i++)
{
for (int w = wt[i]; w <= W; w++)
{
dp[w] = max(dp[w],
dp[w-wt[i]] + val[i]);
}
}
所以一定要记住:
0/1:从大到小。
完全:从小到大。
但不要只背方向。
真正原因是:
0/1 背包不能让同一个物品在这一轮被重复使用;完全背包允许重复使用。
八、6 月份第二道编程题:《满二叉树》
题目给出:
n 个节点
每个节点:
左儿子编号
右儿子编号
然后要求:
统计所有子树中,有多少棵是满二叉树。
它的核心状态实际上只有两个:
h[u]
chk[u]
其中:
h[u]
表示:
u这棵子树的高度。
而:
chk[u]
表示:
u这棵子树是不是满二叉树。
九、这个状态转移非常重要
对于节点 u:
h[u] = max(h[left], h[right]) + 1;
如果:
左子树是满二叉树
并且
右子树是满二叉树
并且
左右子树高度相同
那么:
chk[u] = true;
即:
chk[u] =
chk[left]
&& chk[right]
&& h[left] == h[right];
这就是非常漂亮的树形 DP:
u
/ \
L R
↓ ↓
已知L的信息
已知R的信息
↓
计算u的信息
十、我对明天两道编程题的"押题"建议
我给六级学生做最后一晚的优先级排序,这样安排:
★★★★★ 第一档
树形 DP
至少练会:
-
二叉树 DFS
-
子树高度
-
子树节点数量
-
子树和
-
判断某种性质
-
dfs(left); dfs(right);后处理
★★★★★ 第二档
0/1 背包
一定会写:
for (int i = 0; i < n; i++)
for (int w = W; w >= wt[i]; w--)
dp[w] = max(dp[w],
dp[w-wt[i]] + val[i]);
★★★★★ 第三档
完全背包
一定会写:
for (int i = 0; i < n; i++)
for (int w = wt[i]; w <= W; w++)
dp[w] = max(dp[w],
dp[w-wt[i]] + val[i]);
6 月份《条形蛋糕》就是非常典型的完全背包模型。
十一、选择题复习看什么?
六级除了两道编程题之外,选择题覆盖了:
-
继承与多态
-
虚函数
-
栈
-
循环队列
-
完全二叉树
-
二叉树遍历
-
哈夫曼
-
格雷码
-
BFS
-
BST
-
线性 DP
-
0/1 背包
十二、明天考前最后复习清单
我要求大家拿着这张清单过一遍:
DP
dp[i]到底表示什么?初始化是什么?
状态转移是什么?
最终答案在哪里?
0/1 背包为什么倒序?
完全背包为什么正序?
树
TreeNode左右孩子
dfs(u)空节点
if (!u) return先 DFS 子树
再计算当前节点
高度
子树大小
子树和
判断子树性质
二叉树
前序:根左右
中序:左根右
后序:左右根
层序:BFS + queue
完全二叉树
BST
STL
stack
queue
priority_queue
sort
vector
C++
指针
引用
类
继承
虚函数
虚析构
构造函数不能是
virtual
判断题也会考到构造函数、析构函数、stack::pop()、BST、完全二叉树、非递归前序遍历以及 DP 等内容。
十三、最后给六级学生一句话
明天如果第一道编程题看到"选物品、容量、价值、次数",先想背包;
如果看到"节点、儿子、子树、整棵树的信息",先想 DFS + 树形 DP。
不要怕题目换皮。
题目可以换故事、换数据、换问法,但算法的骨架不会凭空消失。
6月份的考题,提供了非常清晰的训练样本:
第一题《条形蛋糕》→ 完全背包
第二题《满二叉树》→ 树形 DP
这两道题正好可以作为明天考前的最后一轮"模型识别训练"。
所以今晚最有效的方式不是再刷 20 道新题,而是把这两道 6 月真题真正做到"能读懂、能手推、能默写核心代码、能解释每一行为什么这样写"。
如果明天真的出现树形 DP + 0/1/完全背包,大家就有很大的机会把这 50 分的编程题稳住;即使题型发生变化,这套"识别模型 → 写状态 → 写转移"的能力也仍然能用。