引例:
如何计算斐波那契数列?
- 递归
- 记忆化搜索
- 动态规划(递推)
动态规划:避免无用的重复计算,提高处理的效率。
将一个问题拆成几个子问题,分别求解这些子问题,即可推断出大问题的解。当子问题具有重叠性时 (同一个子问题多次出现),相比于搜索 (递归),利用 dp 就可以避免重复计算,节省时间。 - 适用条件
最优子结构:大问题的最优解可以由小问题的最优解推得。
无后效性:当前问题的解只与已求得问题 (过去) 的解有关,而与未求得的问题 (未来) 的解无关。
解决方法:
- 确定状态
状态是比较抽象的,如何正确的定义状态需要多做题,不同类型
的 dp 有各自的套路。 - 确定转移方程
当前状态可以影响后续的哪些状态or当前状态可以被之前的哪些状态影响。
最为经典的背包DP
- 01 问题详情
有 n 种物品和一个容量为 m 的背包。每种物品只有一件。第 i种物品的大小是 wi,价值是 vi,求如何使装入背包中物品的价值最大?
B站视频讲解
这个极其清楚 - 02 解题思路
确定状态:f[i][j]:只选择前 i 种物品,大小总和为 j 时的最大价值。
转移方程:
f[i][j] = max(f[i − 1][j], f[i − 1][j − w[i]] + v[i])
参数解释:f[i − 1][j] 不装第i件物品的价值。
f[i − 1][j − w[i]] + v[i] 装了第i件物品(价值增加,容量减少)
注意:应该是先从第1件物品向下推。
示例代码:
#include <iostream>
//Use C to make a knapsack
#define N 6
#define W 21
int B[N][W]={0};
int w[6]={0,2,3,4,5,9};
int v[6]={0,3,4,5,8,10};
//w:容量
//v:对应的价值
//数组0的地方不用方便读取
using namespace std;
void knapsack(){
int k,C;
//c是当前剩余容量
//k是前多少件物品的最大价值
//当然是从第一件物品往里面放的话更顺一点
for (k = 1; k < N; ++k) {
for (C = 1; C < W; ++C) {
if(w[k]>C){
//如果当前背包放不进去,那这件物品就不放进去了
B[k][C]=B[k-1][C];
} else{
int value1=B[k-1][C-w[k]]+v[k];
//是买第K件商品,然后剩下的空间去买k前面的商品的总价值高
int value2=B[k-1][C];
//还是不买第k件商品的总价值高
//这个相当于不放第K件商品进去
//比较哪个最大价值更大
if(value1>value2){
B[k][C] = value1;
}else{
B[k][C] = value2;
}
}
}
}
}
int main() {
knapsack();
cout<<B[5][20]<<endl;
return 0;
}
多重背包 (每个物品有k件)
完全背包(物品变成了无数件)
转移方程
由于背包容量有限,每种物品选取的个数是有限的,所以可以转化为多重背包。
完全背包的最暴力代码:
同理,多重背包的第三次循环判断条件不是容量,是在满足容量要求的同时,满足数量的要求。改变第三次循环的条件即可:
需要注意的是,以上代码并没有进行优化,均为背包问题的最简单解法,常用的优化方法如下:
01背包 | 多重背包 | 完全背包 |
---|---|---|
滚动数组、一维数组 | 滚动数组、一维数组,二进制优化 | 一维数组 |
多重背包和完全背包参考:
cqh小蒟蒻 背包讲解
背包九讲貌似是一个很不错的资料,可以看看。