01背包

01背包

题目描述

在这里插入图片描述


核心思路

最大价值物品数量i背包容量j的函数。

设函数 f [ i ] [ j ] f[i][j] f[i][j]表示前i件物品放入容量为j的背包最大价值

那么最终的最大价值就是物品数量i从0增长到 n n n,背包容量j从0增长到 m m m f [ n ] [ m ] f[n][m] f[n][m]的值。

在这里插入图片描述

问题:为什么这里的i和j都是从1开始呢?

因为由表可知,当i和j为0时,表示没有一件物品,背包容量为0,所以值为0。因此,当i和j为0时,可以不用看,它的值肯定为0,又由于我们定义的f数组是全局的,所以初始化本来为0了。

在这里插入图片描述

填表过程如下:

当i=1时:

在这里插入图片描述

当i=2时:

在这里插入图片描述

当i=3时:

在这里插入图片描述

这里的时间复杂度和空间复杂度都是 O ( n m ) O(nm) O(nm),但是由图表可知,第i层的数据只会用到第i-1层的数据,而不会用到第i-1层之前的数据了。

我们可以去掉第一维,用一维数组 f [ j ] f[j] f[j]只记录一行的数据,考虑如果让j顺序循环,顺序更新 f [ j ] f[j] f[j]会怎么样呢?

如下图,可以发现当i=1时,j=6时,想要更新f[6]。其中f[6]=f[3]+5,但是这里用的f[3]是第i=1层时已经被更新过的f[3]=5,而不是用上一层第i-1层级第0层的f[3]=0,因此此时得到的f[6]=5+5=10。但是由左图可知,但i=1,j=6时,f值应该为5而不是10,所以当j顺序循环时得到的f值是错误的!

在这里插入图片描述

错误原因就在于j-w[i]<j,因此f[j-w[i]]会比f[j]先得到更新,由右边写法的else语句可知,那么就可能会用已经更新过后的f[j-w[i]]去更新f[j]的值,那么就会出错。

但是对比左侧的else写法可知,第i层的f值是由第i-1层的f值来更新的,因此如果去掉第一维后,如果j是顺序循环,那么就是用第i层已经被更新的f[j-w[i]]去更新第i层的f[j],这就错了。

正确做法:

在这里插入图片描述

终极写法:

这里把if语句去掉了,但是第二个for循环中,条件就是 j ≥ w [ i ] j\geq w[i] jw[i]了,不然 j − w [ i ] j-w[i] jw[i]可能回是负数。

在这里插入图片描述


代码

朴素写法:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
//v是物品的体积   w是物品的价值
int v[N],w[N];
//f[i][j]表示前i件物品放入背包容量为j时的最大价值
int f[N][N];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&v[i],&w[i]);
    //从第一件物品循环第n件物品
    for(int i=1;i<=n;i++)//枚举物品的数量
    {
        for(int j=1;j<=m;j++)//枚举物品的体积
        {
            if(j<v[i])
                f[i][j]=f[i-1][j];
            else
                f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    printf("%d\n",f[n][m]);
    return 0;
}

降为一维后:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
//v是物品的体积   w是物品的价值
int v[N],w[N];
int f[N];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&v[i],&w[i]);
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=1;j--)//逆序更新
        {
            if(j<v[i])
                f[j]=f[j];
            else
                f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    printf("%d\n",f[m]);
    return 0;
}

降为一维后的终极代码:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
//v是物品的体积   w是物品的价值
int v[N],w[N];
int f[N];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&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]);
    }
    printf("%d\n",f[m]);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

卷心菜不卷Iris

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

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

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

打赏作者

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

抵扣说明:

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

余额充值