解题:
二维:
1.定义数组: f(i,j)表示前 i个物品,背包容量 j 下的最优解(最大价值)
2.状态转移:f(i,j)可以由什么转移过来,
(1)当前背包容量不够:(j < v[i]),没得选,因此前 i个物品最优解即为前 i−1 个物品的最优解:f(i,j)=f(i-1,j)
(2)当前背包容量够:可以选,因此需要决策选与不选第 i个物品:选:f(i,j)=f(i-1,j-v[i])+w[i];
不选:f(i,j)=f(i-1,j);
最后选与不选取最大值即可;
3.初始化:f(0,0)可以根据定义来决定,值为0。
4.递推:从开始的状态递推到最终的状态。
优化为一维:
1.为什么优化:节约空间,方便书写。
2.为什么可以优化:因为我们转移的时候都是用到了同一层状态,所以我们保证递推的时候从哪一层转移来的不变即可。
3.怎么优化:递推的时候保证从后往前即可,因为要保证:在用 i-1 轮的状态的时候没有使用第 i 轮的状态。
例如,一维状态第 i 轮对体积为 3 的物品进行决策,则f[7]由f[7-3]更新而来,这里的f[4]正确应该是f[i - 1][4],但从小到大枚举到7 的时候 f[4] 在第i轮计算却变成了 f[i][4] 。我们再更新f[7]的时候,就错误了。
如果使用顺序,会先更新f[4],再更新f[7],对于这个书包问题来讲,就是有可能,在更新f[4]的时候,已经把这次能加的物品加进来了,然后更新f[7]的时候,还有可能再加一次,所以必须使用逆序,保证,f[4]是没有加入新物品前,背包里的最优解。
代码://dp 思考思路: //1.状态表示:多积累经验:所有的状态计算及边界 都围绕所表示的状态; //2.选择问题:如背包问题:第一维表示物品:后边的维度表示限制; //状态计算: //一定要考虑所有情况;求最值可以重复; //dp:优化;朴素做法,然后对代码做等价变化;思路不能变; #include <iostream> #include <algorithm> using namespace std; const int N=1010; int v[N],w[N]; int f[N][N];//状态表示; //前i个物品体积为j的所有方案; //属性:价值最大值; //状态转移,选与不选·; int n,m; int main() { cin>>n>>m; for(int i=1;i<=n;i++) { cin>>v[i]>>w[i]; } //f[0][0~m]表示从0个物品价值0~m的最大值:为0; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { //当前背包不能装下第i个物品;价值为i-1的物品价值; if(j<v[i]) { f[i][j]=f[i-1][j]; } //可以装下,不要上一个物品; //还是装这个物品,决策第i个物品; //因为每一次都是由上一次而来,所以为表示最大值 else //只有一个的时候已经存完了每一个物品的价值;f[1][0~j]; f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]); //优化为一维后f[i-1]要先被计算计算;所以逆序; //举个例子:一个物品价值2;另一个价值3;体积都为2; //j为3时;f[2][3]=max(f[1][3],f[1][3-2]+w[2]; } cout<<f[n][m]; return 0; }
一维:
#include <iostream> #include <algorithm> using namespace std; const int N = 1010; int n, m; int v[N], w[N]; int f[N]; int main() { 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 = m; j >= v[i]; j -- )//找每一次决定的最优解; f[j] = max(f[j], f[j - v[i]] + w[i]); cout << f[m] << endl; return 0; }
01背包详解--模板
于 2024-02-12 20:17:23 首次发布