DP问题通常要分为两部分看待
第一类就是状态表示
通常用个数组f[i][j]来表示,这个数组主要表示的是在前i个物品中选择在体积不超过j的情况下能取得到的最大值
第二类是状态计算
我们将情况分为了在前(0,1,2,3.......i)种选择,他们在体积不超过(0,1,2,3,4.......j)的情况下所能取得到的最大值
个人理解
我一直觉得背包问题有一种特性,就是他复现并且使用了过去的情况,然后让他尝试加上一个新的值,在过去他是最大的,在现在他加上一个值那么他还是最大的
f[i][j] = max(f[i - 1][j] , f[i - 1][j - v[i]] + w[i];
//更新当前的值 不加入新元素的值 尝试加入新元素之后的值
因为我们一直保证f[i][j]这个权值一直是所挑选出来的最大的,要想更新他的值也要在他本来就是“老大”的基础上尝试,这样就不会漏解
这里我们就复现了以前的状态:如果要加入第i个物品,如果直接算只能暴力搜索,为了追求效率,我们采用了一种“曲线救国”的方式,原先的要求是在不超过最大容量N的情况下要求他能娶到的最大值,我们可以绕弯一下,我们在不超过最大容量为N-v[i]的情况下取到的最大值,之后我们在加上去掉的第i个物体的权值就可以了。
不论是01背包还是别的背包模型都会用到这种思路,我觉得这就是对过去状态的复用
完全背包与01背包的不同
他俩之间的区别有这几点,01背包对于每个物品能选择用不用,用的话只有一个。
完全背包对于每个物体能选择用不用,用的话有无限个。
所以我们要讨论的情况就变多了
改变的状态计算
在这里f[i][j]数组依旧表示的是去前i个物品,在体积不超过j的情况下能取到的最大值
这里多的情况就是,在01背包模型里头,我们可以选择加入第i个物品,但是在完全背包问题里头我们不止要考虑加入第i个物品,我们还要考虑加几个第i个物品呢?
我们用W数组来表示权值
//01背包模型的复现,我们只需要选择是否加入这一个物品就可以了,复现的一种情况。
f[i][j] = max(f[i - 1][j] , f[i - 1][j - v[i]] + w[i];
//在完全背包模型里头,我们复现的情况就变多了,我们要复现的是再加入k(k=0,1,2....)个i之前的情况
//我们最后要求的就是在所有的复现情况中的最大值也就是
f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2*v[i]]+2*w[i],......)
如何优化?
在这里优化方法不是硬推公式也不是加循环
我们采用了一种类似高中数列求和的一种办法,就是后退一位,找到错位之后的位置关系
f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2*v[i]]+2*w[i],......)
f[i][j-v[i]] = max( f[i-1][j-v[i]],f[i-1][j-2*v[i]]+w[i]...........
以此类推,我们发先错位之后,2式子再加上一个w[i]就是1式除了第一项剩下的那一坨!
经过优化之后我们就可以写出来之后的代码
f[i][j] = max(f[i-1][j],f[i][j-v[i]]+w[i]);
最终代码
#include<iostream>
using namespace std;
const int N = 1010;
int v[N] , w[N] , f[N];
int main(){
int n , m ;
cin>>n >> m;
for(int i = 1 ; i <= n ; i ++){
cin>>v[i]>>w[i];
}
for(int i = 1 ; i <= n ; i++){
for(int j = v[i] ; j <= m ; j++){//必要条件就是当前体积必须要大于这个物体的体积才能装进去
//因为我们用到的状态就是f[i][j-v[i]] + w[i]所以顺序遍历把最小的值先更新,正合我们意
f[j] = max(f[j] , f[j - v[i]] + w[i]);
}
}
cout<<f[m];
return 0;
}