[AcWing] 2. 01背包问题(C++实现)0-1背包问题模板题

如果只看为什么要逆序,请直接下拉至3. 解法后附的可能存在的问题.
3. 解法后附的可能存在的问题,我给出了一个实例,可以清楚地看到,为什么正序会影响上一层的结果,而为什么逆序不会影响上一层的结果。

1. 题目

在这里插入图片描述

2. 读题(需要重点注意的东西)

思路:
闫式dp分析法
在这里插入图片描述


用闫式dp分析法分析0-1背包问题

未进行优化
在这里插入图片描述


能不能进行优化?


在这里插入图片描述
由于 i 这一层只与 i - 1 这一层有关,则可以使用滚动数组来优化空间复杂度,仅保留上一层结果即可,这样就能将其优化为一维:

    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(j > v[i]) f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }

但是此时,我们相当于用的是 f [ i ] f [ j - v[ i ] ]的状态,而并非 f [ i - 1] f [ j - v[ i ] ]的状态,因为 f [ i ] 把 f [ i - 1]的状态更新了。

因此,我们要想使用上一层的 j - v[i],就需要把 j 逆序遍历。

    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]);

3. 解法

---------------------------------------------------解法1:优化前---------------------------------------------------

#include<iostream>
using namespace std;

const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][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 = 0;j <= m;j++){
            f[i][j] = f[i-1][j];
            if(j >= v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    cout << f[n][m]<< endl;
    return 0;
}

---------------------------------------------------解法2:优化后---------------------------------------------------

#include<iostream>
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 = 0; 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;
}

可能存在的问题
① 优化前为什么 要判断 if(j >= v[i]) ?

我们在此刻面临着选不选 i 的问题,当前的背包容量是 j ,如果把背包全部清空都放不下 i ,那么只能将 i 舍弃,即为不选 i ,则为f[i][j] = f[i-1][j];
如果能放下 i ,选择 i 时的最大价值 = i 的价值 + 在剩下 i-1 个物品中选体积小于j-v[i]的最大价值,即max(f[i][j],f[i-1][j-v[i]]+w[i])

事实上,代码也可以这样写,表示选 i 和不选 i 两种情况

    for(int i = 1;i <= n;i++){
        for(int j = 0;j <= m;j++){
            if(j <= v[i]) f[i][j] = f[i-1][j]; // 如果背包清空了也放不下i,则不选i
            else {
            	f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]); // 如果背包能放下i,则在i-1个物品中,选体积小于j-v[i]的价值最大者
            }
        }
    }

优化后,为什么要逆序?

如果不逆序,代码为:

    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(j > v[i]) f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }

此时,我们提出一种可能的情况:

最外层 i 循环到i时,v[i] = 1 , m = 5,我们会将 j 循环 1 2 3 4 5 ,

j = 2时,我们需要判断 f [ 2 ] = max ( f [ 2 ], f [ 1 ] + w [ i ] ) ;
如果此时,我们将f [ 2 ] 更新了,那么在下一个 j 循环,j = 3 时,

我们要判断 f [ 3 ] = max ( f [ 3 ], f [ 2 ] + w [ i ] ) ;

那此时,我们使用的就是刚刚被更新的f [ 2 ],即 f [ i ] [ 2 ],而非上一层的 f [ i-1 ] [ 2 ],因为上一层的 f [ i-1 ] [ 2 ]已经被更新成当前层的f [ i ] [ 2 ]了


注意:逆序后,我们的循环条件是j >= v[i],代码为:

for(int i = 1; i <= n; i++)
    for(int j = m; j >= v[i]; j--)  
        if(j > v[i]) f[j] = max(f[j], f[j - v[i]] + w[i]);

又因为,循环条件是j >= v[i],所以 if 判断语句也可以删除,即:

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]);

此时,我们再来看刚刚那种情况,看更新当前层(第 i 层)f [ j ] 是否会影响上一层(i - 1层)的f [ j ]

最外层 i 循环到i时, v[i] = 1 , m = 5,我们会将 j 循环 5 4 3 2 1,

j = 5时,我们需要判断 f [ 5 ] = max ( f [ 5 ], f [ 4 ] + w [ i ] ) ;

看见了吗,我们此时只会更新 f [ 5 ] 的值,在下一次判断时

我们需要判断 f [ 4 ] = max ( f [ 4 ], f [ 3 ] + w [ i ] ) ;

发现了吗,刚刚更新的值f [ 5 ]与下一次的判断f [ 4 ]无关,因此,只需要将 j 逆序遍历,就不会影响上一层的f[j - v[i]]

4. 可能有帮助的前置习题

5. 所用到的数据结构与算法思想

  • 动态规划
  • 0-1背包问题

6. 总结

0-1背包问题模板题,理解思想并背下代码。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cloudeeeee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值