动态规划
动态规划的“状态”和“转移”等概念可能较为抽象,也过于灵活,我列出了较多的例题帮助思考。
动态规划的难点在于状态设计和子结构的挖掘。
原理
对于递推,我们可以做进一步抽象。
还是 Fibonacci 数列,我们把
此公式称为动态转移方程,它描述了状态之间的关系。最后是边界
很简单吧?来点基础模型。
矩阵取数
给定一个
的矩阵 (数字三角形也差不多),从左上角开始,只能向下或向右走,求路过所有数字之和最大值。
可以先思考一下,这道题怎么设状态比较好。
定义状态
而且由题知,一个位置只可能被从上面和左面过来。于是我们可以得到状态转移方程
即两者取大。边界是显然的
状态
01 背包
给定
个物品,第 个物品的体积为 ,价值为 。有一容积为 的背包,求背包内能放下的最大价值。
设状态
- 若不选第
个物品,则 。 - 若选第
个物品且 ,则 。
于是转移方程为两者取
cpp
f[0][0] = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= m; j++)
f[i][j] = f[i - 1][j];
for (int j = v[i]; j <= m; j++)
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
f[0][0] = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= m; j++)
f[i][j] = f[i - 1][j];
for (int j = v[i]; j <= m; j++)
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
滚动数组
注意到阶段
然而在第
怎么办?让循环倒着来,这样后面的先更新,前面不会被影响。
cpp
f[0] = 0;
for (int i = 0; i < n; i++) {
for (int j = m; j >= v[i]; j--) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
f[0] = 0;
for (int i = 0; i < n; i++) {
for (int j = m; j >= v[i]; j--) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
同样,矩阵取数也可以使用滚动数组。
完全背包
给定
种物品,第 种物品的体积为 ,价值为 ,每种有无限个。有一容积为 的背包,求背包内能放下的最大价值。
自己尝试推一下,会发现正序循环恰好是所求解。
cpp
f[0] = 0;
for (int i = 0; i < n; i++) {
for (int j = v[i]; j <= v[i]; j++) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
f[0] = 0;
for (int i = 0; i < n; i++) {
for (int j = v[i]; j <= v[i]; j++) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}