DP 01背包问题

目录

一、01背包问题

(一)二维

(二)优化一维


一、01背包问题

例题

首先分析这个题目,用y总的背包问题分析图解

(一)二维

然后我们来看这个题目,它有i个物品,物品体积为v[i],价值为w[i],

用f[i][j]来表示,从前i种物品中选择体积不大于 j 的物品,使得价值达到最大,f[i][j]存储的就是这个最大值。

然后我们来看怎么去计算,即怎么区划分最大值

每个物品都是不同的,并且注意它们任何一个都只能被选择一次,

那我们来看看,目前的的集合是如何由上一个集合转换过来的

设上一个集合为f[i-1][j]
那么现在的集合是f[i][j]

对于第i个物品,由于只能选择一次,那么我就只有选或者不选
(1)如果不选取第i个物品 
那么f[i][j]就是f[i-1][j],两者表示的最大值就是相同的

(2)如果选择第i个物品
那么现在的f[i][j] 就是f[i-1][j-v[i]]+w[i] 
用j-v[i]直接将v[i]的体积减去,就表示必定选择,注意要加上i的价值w[i]

最后只要比较(1)和(2)的大小就可以求解了
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])

但是有一个问题,就是我能选择第i个物品的前提是我能放得下,
就是j-v[i]>=0
所以在(2)的前面要加上条件j>=v[i]

那么我们的代码就是

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=1010;
int v[N],w[N];//分别表示第i物品的体积和价值
int f[N][N];//表示前i个物品,在体积不超过j的情况下的最大值

int main()
{
    int n,m;//输入n个物品 总体积为m

    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++)//对n个物品从1到n一次枚举
       for(int j=0;j<=m;j++)//体积在每个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",f[n][m]);
    return 0;
}

(二)优化

如何去优化呢?

那就去看如何在划分集合的时候做优化

这是我们现在的代码

 for(int i=1;i<=n;i++)//对n个物品从1到n一次枚举
       for(int j=0;j<=m;j++)//体积在每个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]);
       }

从二维到一维 那我们去掉第一维也就是和i有关的,那么就是

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

现在可以发现f[j]=f[j]就是一个恒等式,可以直接去掉

 现在就剩下这个 f[j]=max(f[j],f[j-v[i]]+w[i]);执行的条件是j>=v[i],

那我们把条件放到循环上去就得到

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

可是现在我们再去看循环发现了一个问题,就是j-v[i]小于j[i],那么j-v[i]会先被执行

那会发生什么样的问题呢,

我们来看一开始对f[i][j]的更新方式

f[i][j]是由f[i-1][j]更新而来,即使上一个状态更新而来,
f[i][j]与f[i-1][j]的区别就是第i个物品
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
里面的f[i-1][j-v[i]]+w[i]]代表的就是对第i个物品的更新
如果表示成f[j]=max(f[j],f[j-v[i]]+w[i])代表的就是对第i个物品的更新  (1)

但是现在f[j]=max(f[j],f[j-v[i]]+w[i]);
j-v[i] < j
那么如果从小到大遍历
f[j-v[i]]就会先被更新,再用来更新f[j];
设现在k=j-v[i]
f[k]=max(f[k],f[k-v[i2])
也就是说k由i2更新而来 即 f[j-v[i]]代表对第i2个物品的更新与(1)矛盾

那怎么办呢

我们发现只要让f[j-v[i]]后于f[j]更新即可

所以只要在遍历j的时候从大到小遍历即可

得到最终代码

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=1010;
int v[N],w[N];
int f[N];

int main()
{
    int n,m;
    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",f[m]);
    return 0;
}

题目来源acwing 

模板来源:闫学灿
链接:https://www.acwing.com/activity/content/code/content/57785/
来源:AcWing
 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值