DP问题:¶
- 背包问题
- 线性DP
- 区间DP
- 计数类DP
- 数位统计DP
- 状态压缩DP
- 树形DP
- 记忆化搜索
通用DP思维¶
- 状态表示
-
集合:
- 所有只从前i个物品选取,总体积<=j的宣发
- 属性:Max,Min,数量
- 状态计算:
- 集合划分:做到不重不漏
- 不选的情况:没选的前0~n个,以及容量为0时的所有情况。
- 含i:f(i-1,j)
- 不含i:f(1i,j-v1)+v2,从1i中选,总体积至少要保证当前v1加上够用。
- 根据属性,对含i和不含i的结果进行综合
- 所有只从前i个物品选取,总体积<=j的宣发
-
滚动数组¶
每次都只根据当前层和上一层来算,那么这种二维DP就可以优化成滚动数组:即把上一次的结果当做上一层拿来重新计算当前层
01背包¶
推导:f[i,j] = MAx(f[i-1,j], f[i-1,j-v[i]] + w[i])
每种只拿一个,那么只能完全根据上一层结果算,不能利用当前计算过后的结果再次计算。
滚动数组优化:由于计算需要拿前面的计算结果,所以新的计算先算出后面的结果再往前算,可以保证前面的历史结果不被覆盖。
完全背包¶
1.暴力做法¶
本质上是f[i,j] = f[i - 1, j - v[i] * k] + w[i] * k
,(v是大小,w是价值),k理论上可以取无限大,只要小于背包大小。
但这样时间复杂度过高,因为要判断范围内所有的可选k值的结果,ijk三重循环。
2. 优化做法(常规做法):¶
f[i,j]
和f[i,j-v]
推导式可能差一个v[i] * (k-(k-1)) == v[i]
.
因此,咱们优化的方式,即利用之前计算过的n个k的结果。
推导:f[i,j] = MAx(f[i-1,j], f[i,j-v[i]] + w[i])
可以看到只与01背包有个i和i-1的区别。01只拿上一层,而完全需要利用当前层计算的结果。
每种可以拿无数个,根据上一层结果和当前已计算的结果继续叠加,可以利用当前计算过后的结果再次计算。
3. 滚动数组优化¶
滚动数组优化:正序计算,利用到算出的结果
多重背包¶
需要滑动窗口最大值的思想,可以用单调队列。
1.暴力做法¶
本质上是f[i,j] = f[i - 1, j - v[i] * k] + w[i] * k
,(v是大小,w是价值)
推导方程其实和完全背包一样,不过k不是根据背包算,除了完全背包的小于背包大小。还有自己的数量限制。
2. 完全背包的优化方式是否可行?¶
请看,若一个背包大小顶多装下s个v
f[i,j]
不能利用前面算出来的值(因为数量有限,前面可能已经把数量用光了)
3. 01背包拆解¶
可以把容量为s的背包暴力的全部拆成s个01背包,时间复杂度n v s
4. 二进制优化拆解01背包¶
2000可以拆成1,2,4,8·····等多个背包,时间复杂度直接变为n v log2S。
int cnt = 0;
for(int i = 1;i <= n;i ++)
{
int a,b,s;
...
int k = 1;
//二进制分组
while(k <= s)
{
cnt ++;
v[cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2;
}
//剩余的再分一组
if( s > 0 )
{
cnt ++;
v[cnt] = a * s;
v[cnt] = b * s;
}
}
分组背包问题¶
每一组只能选一个,但是每一个物品的大小和价值也都不一样。
dp公式:f[i-1, j - v[i,k]] + w[i,k]