背包问题——2. 简单01背包

简单01背包问题

#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)

using namespace std;

const int maxn=1010,maxv=1010;
int v[maxn],w[maxn];  //v体积,w价值
int dp[maxv];

int main()
{
    int N,V;
    read(N,V);
    for (int i=1;i<=N;i++)  read(v[i],w[i]);
    
    for (int i=1;i<=N;i++)
        for (int j=V;j>=v[i];j--)"更新当前物品在所有背包体积下的最优选择,不能选择的就不更新了,直接继承"
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
            
    printf("%d",dp[V]);
    return 0;
}

题目描述

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2   体积、价值
2 4
3 4
4 5

输出样例:

8

分析:

对每个物体,有两种状态:选或者不选,而且必然能够 不选择 这个物品,但是 能否 选这个物品还要取决于当前的背包体积:

  1. 如果物体的体积小于等于当前背包体积,有选和不选两种状态;
  2. 如果物体的体积大于当前背包体积,就只有不选这一种状态。

二维数组实现

#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)

using namespace std;

const int maxn=1010,maxv=1010; //最多1000个物品,价值最大为1000
int v[maxn],w[maxn]; //记录每个物品的体积v还有价值w,从下标1开始存物品,方便和dp数组对应
int dp[maxn][maxv]; //选择前N个物品,体积最大为V

int main()
{
    int N,V;
    read(N,V);
    for (int i=1;i<=N;i++)  read(v[i],w[i]);  "从下标1开始存储物品,和dp数组对应"
"开始递推过程,递推方程:dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);"
    for (int i=1;i<=N;i++)  //先循环物品,再循环体积,先行后列。
        for (int j=1;j<=V;j++) { //从0开始最好,0~V
            dp[i][j]=dp[i-1][j];   "不选第i个物品是必然成立的,如果他不能选择第i个物品就直接继承上一行"
            if (j>=v[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);   
        }          "在可以选择第i个物品的条件下,判断是否要选择第i个物品"
    printf("%d",dp[N][V]);
        
    return 0;
}

dp[i][j]表示在前 i 件物品,背包重量为 j 的前提下,各种选法的最大价值:
如果物品能放入背包,则动态转移方程为 dp[i][j]=max( dp[i-1][j],dp[i-1][ j-v[i] ]+ w[i] ), j>=w[i]
如果物品不能放入背包,dp[i][j]=dp[i-1][j] ,j<w[i]

然而,每个物品选择不放入背包的是必然要有的,所以也可以选择上面代码中的写法。

递推核心: 选择前i个物品的最优解时,只需在选择前i-1个物品的最优解的基础上,判断是否选择第i个物品即可,不同容量的背包下,是否选择第i个物品是不一样的。

最后结果:二维dp表记录了所有情况下的物品选择的最大体积,我们只需要最后的在选择前N个物品,背包体积为V的情况下的最大价值,即dp[N][V]

先循环物品,再循环体积 ?先循环体积,再循环物品

我们观察到这个动态转移方程,在第i行时,只需要往上一行的数据,即第i-1行;在第j列时,也只取决于第j列及其左边列的数据,即前j列。 ,即更新dp[i,j] 时,每次只会用到dp二维表中的第i-1行,从dp[i-1,0]dp[i-1][j] 中的数据,这为我们后续的优化提供了基础,而且我们要 考虑遍历次序问题

  1. 从上往下遍历,先行后列,即先循环物品,再循环体积。每个物品下有V个容量的背包需要确定最佳选择,按行来更新,每往下走一行,代表多一个物品。
  2. 从左到右遍历,先列后行,即先循环体积,再循环物品。每个体积下有N个不同的背包,在走第一行时,选择前1个物品,在走第二行时,选择前2个物品,需要第一行的数据,……,一直到第N行,需要第N-1行的数据,这样一直循环V列。

不过要注意两种遍历方式下的初始化问题:

  1. 从上往下遍历,求dp[i][j]时,只会用到第i-1行的所有数据,为了防止数组越界,不能从第0行开始处理,要从第1行开始,所以要先把第0行初始化为0,即不选择任何物品在各种体积下的最大价值,即为0。然后,物品从1开始枚举,背包体积从0开始枚举。
for (int i=1;i<=N;i++) 
        for (int j=0;j<=V;j++)

背包体积之所以从0枚举到V,这是为了防止当v[i]==j时,j-v[i]=0,又回到第0列的情况,因此必须每行的第0列都需要维护,实际上,当j为0时,只有dp[i][0]=dp[i-1][0],(除非有体积为0的物品),所以dp[i][0]最终是会等于dp[0][0],即第0行初始化的0,所以可以在初始化时也直接把第0列初始化为0,那么物品和背包体积都可以直接从1枚举了,即上面代码中所示:

for (int i=1;i<=N;i++)  
        for (int j=1;j<=V;j++)
  1. 从左到右遍历,求dp[i][j]时,可能要用到左边前j列上第i-1行的数据,包括刚刚更新的上一行的一个数据dp[i-1][j],因此为了防止数组越界(-1),必须背包体积从1开始枚举,物品从1开始枚举,且必须是从左到右、从上到下枚举下一行需要用到上一行的数据,所以初始化时,必须第0行,第0列都要初始化为0。
for (int j=1;j<=V;j++) 
        for (int i=1;i<=N;i++)

初始化第0行的含义:第0行代表什么也不选,所以不管背包体积为多少,价值都为0,所以dp[0][j]=0;
初始化第1列的含义:不管选什么物品,我背包体积限制为0,所以你的价值也必须为0。所以dp[i][0]=0;

滚动数组实现

我们采用先循环物品,再循环背包体积的方法,因为他每次只会用到上一行的数据,那就定义一个二维数组,2行V列:int dp[2][V];

如果是先循环背包体积,再循环物品,在求完dp[i-1][j]后,求dp[i][j]时,他可能就要用到前j列上第i-1行的数据,甚至必须用到刚刚求的dp[i-1][j],而这不止需要两列维护,因此只有第一种循环方式才能用滚动数组优化。

  1. 可以通过异或操作,定义一个变量p,如果p=0,那么p^1=1;
    如果p=1,那么p^1=0,如此可以在第0行与第1行之间反复切换。
  2. 通过操作,遍历物品的i,即可代表行数,i&1可以取出其最低位,为偶数时,i为0;为奇数时,i为1,以此确定第0行还是第1行。
#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)

using namespace std;

const int maxn=1010,maxv=1010;
int v[maxn],w[maxn];  //v体积,w价值
int dp[2][maxv];

int main()
{
    int N,V;
    read(N,V);
    for (int i=1;i<=N;i++)  read(v[i],w[i]);
    //dp递推,预处理:0行0列初始化为0,体积可以从1处理
    int p=0;   "每次循环结束时,p指向处理完后的那一行,上次处理的是第0行。"
    for (int i=1;i<=N;i++) {
        for (int j=1;j<=V;j++) {  //p^1表示当前处理的行
            if (j>=v[i]) dp[p^1][j]=max(dp[p][j],dp[p][j-v[i]]+w[i]);   //可以选择物品i,在是否选择之间抉择
            else  dp[p^1][j]=dp[p][j];     //不能选择物品[i],直接继承上一行
        }
        p^=1; "p=p^1; 在在第0行和第1行之间来回切换"
    }
    printf("%d",dp[p][V]);
    
    return 0;
}

一维数组实现

滚动数组的优化是通过第i行的计算只用到了第i-1行的数据的性质,那么再具体点:
更新dp[i,j] 时,每次只会用到dp二维表中的第i-1行,从dp[i-1,0]dp[i-1][j] 中的数据。

因此,可以优化为一维数组,可以知道 背包体积j 越小,他就要被用到的次数越多,会被下一层背包体积j 到V之间用到。因此第二层for循环时采用倒序,求dp[i][j]时,从j=V先入手,因为dp[i-1][V]不会被除了dp[i][V]之外的dp[i][j]用到,这次更新之后dp[i-1][V]就没用了,所以dp[i][V]更新完后就可以覆盖dp[i-1][V]了,依次递减更新。

判定结束的条件是背包容量j是否大于当前要装入背包的物品的体积v[i],因为当j小于v[i]时,由前面知是直接dp[i][j]=dp[i-1][j],继承上一行的数据,由于我们采用一维数组,可直接继承。

#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)

using namespace std;

const int maxn=1010,maxv=1010;
int v[maxn],w[maxn];  //v体积,w价值
int dp[maxv];

int main()
{
    int N,V;
    read(N,V);
    for (int i=1;i<=N;i++)  read(v[i],w[i]);
    
    for (int i=1;i<=N;i++)
        for (int j=V;j>=v[i];j--)"更新当前物品在所有背包体积下的最优选择,不能选择的就不更新了,直接继承"
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);  //进入for循环的都是可以选择物品i的情况
            
    printf("%d",dp[V]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值