你有一个背包,地上一堆物品,挑选一些物品放入背包中,最大能够挑选出来的价值是多少
背包可以装满,背包也是可以不必都装满
一)01背包问题
【模板】01背包_牛客题霸_牛客网 (nowcoder.com)
1)求这个背包最多可以装多大价值的物品?
那么这意味着这个背包可以不用完全装满,那么所能够装的最大价值就是10+4=14,此时装的容量是3,剩余背包容量体积是2
2)若背包恰好装满,求至多可以装多大价值的物品?
如果这个背包恰好装满,那么只能选择2物品和3号物品进行装上,此时装的物品的价值是9,背包剩余容量是0,如果无法将背包装满,就比如说在示例2中,那么返回0
第一问:
一)确定一个状态表示:经验+题目要求
1)背包问题本质上是一个线性的dp问题,当我们在挑选物品的时候,可以从左向右顺序进行挑选,考虑每一个物品是否挑选,从而放到背包里面
dp[i]表示从第一个物品开始进行挑选,挑选到第i个物品的时候所有的选择方法中最大的价值
2)在进行挑选dp[i]的时候是很有可能会使用到之前的状态的,我在进行填写dp[i]的时候,会进行考虑这个i号物品是否进行选择,必须得知道背包容量,不知道背包容量,所以不知道是否可以把物品放进背包;
3)dp[i][j]表示从前i个物品进行挑选,此时体积不超过j的(可以等于j也可以小于j),在这些选法中,所能进行挑选的最大价值,可以等于j也可以小于j
二)根据状态表示推导状态转移方程:根据最后一个位置的状况来进行划分问题:
1)要么我选择第i号位置的物品,要么我不进行选择第i号位置的物品
不选择i物品:dp[i][j]=dp[i-1][j],我们直接在1-i-1物品中找到最大价值,并且体积不超过j
选择i物品:dp[i][j]=w[i]+dp[i-1][j-v[i]]
2)在两种情况中选择一个一个最大值即可,但是此时还需要考虑一个特殊情况,那么有可能是j-v[i]不存在,如果说背包的体积就是10,但是当前物品的体积是11,整个背包都装不下,所以一定要满足j-v[i]是大于等于0的;
3)假设j==v[i]那么dp[i][j]=w[i]
三:初始化+填表顺序+返回值:
1)下标从1开始进行计数,那么原始的dp表就多了一行和多了一列,第一行是0表示没有商品(有包没有能力装任何商品),第一列表示背包的容量是0(商品都装不进去),所以第一行和第一列都是0
2)从上向下填写,每一行从左向右进行填写
3)返回值:返回的是从N个物品中选择,总体积不超过V的商品所有选法中的最大价值
第二问:
一)定义一个状态标识:必须要装满
dp[i][j]表示从前i个物品中进行挑选,总体积此时正好等于j,所有选法中,所能进行挑选物品的最大价值
二)根据状态标识推导状态转移方程:
1)如果选择i位置的商品,那么就去1-i-1中去选择商品组合,选择商品总容量的总体积要等于j-v[i],如果当前礼物的价值已经超过了j(v[i]>j]那么一定不能选择当前商品,如果j==v[i],那么礼物的最大价值就是dp[i][j]=dp[i-1][j-v[i](j-v[i]>=0)
2)如果不选择i位置的商品,那么就去1-i-1中去寻找商品组合,dp[i][j]=dp[i-1][j],但是这个只是不一定存在的,我们可能永远也无法选取到总体积等于j的商品组合,如果dp[i][j]=-1的时候无法选择,总体积是无法凑到j的,但是这个dp[i-1][j]一定存在吗,我的意思是从前i-1个位置的物品进行挑选,选择体积恰好等于j的商品的最大价值,但是有可能选择不到,如果选择不到,那么就让dp[i-1][j]==-1
3)那么为什么不选择dp[i][j]=0来表示从前i个物品中进行挑选,总体积凑不到j的这种情况呢
dp[0][j]表示没有物品进行选择总体积等于j,此时能够选择到的商品的最大价值,其实是可以选到的,不过里面的价值是等于0;
4)对于第一种情况来说,我不进行选择i位置的元素,dp[i-1][j]如果等于-1,那么dp[i][j]=dp[i-1][j]也是可以的
5)对于第二种情况来说,如果选择i位置的商品,必须要进行判断dp[i-1][j-v[i]]不等于-1,如果在i-1区间内找不到礼物价值是j-v[i]的礼物组合,那么dp[i][j]也是-1
三)初始化:
dp[0][0]=0,dp[0][i]=-1,dp[j][0]=0(啥都不选就可以了)
import java.util.Scanner; import java.util.Arrays; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { public static void main(String[] args) { Scanner scanner=new Scanner(System.in); int n=scanner.nextInt();//物品的个数 int V=scanner.nextInt();//背包的体积 int[] v=new int[n+1];//存放物品的体积 int[] w=new int[n+1];//存放物品的价值 //让数组从第一个位置开始进行输入,到时候下标就不用-1了 for(int i=1;i<=n;i++){ v[i]=scanner.nextInt(); w[i]=scanner.nextInt(); } int[][] dp1=new int[n+1][V+1]; for(int i=1;i<=n;i++){ for(int j=1;j<=V;j++){ if(j-v[i]>=0) dp1[i][j]=dp1[i-1][j-v[i]]+w[i]; //如果不进行选择当前i位置的物品放入到背包里面 dp1[i][j]=Math.max(dp1[i][j],dp1[i-1][j]); //如果选择当前i位置的物品放入到背包里面 } } int[][] dp2=new int[n+1][V+1]; dp2[0][0]=0; for(int i=1;i<=V;i++) dp2[0][i]=-1; //此时一件商品都没有,是无法找到一个商品使背包的体积变成j的 //for(int i=1;i<=V;i++) dp2[i][0]=0;//此时啥也不选,就能使背包的体积变成0 for(int i=1;i<=n;i++){ for(int j=1;j<=V;j++){ if(j-v[i]>=0&&(dp2[i-1][j-v[i]]!=-1)) dp2[i][j]=dp2[i-1][j-v[i]]+w[i]; else dp2[i][j]=-1; //如果不进行选择当前i位置的物品放入到背包里面 dp2[i][j]=Math.max(dp2[i][j],dp2[i-1][j]); //如果选择当前i位置的物品放入到背包里面 } } System.out.println(dp1[n][V]); System.out.println(dp2[n][V]==-1?0:dp2[n][V]); //如果最后无法凑出体积是V的情况,那么dp[n][V]是等于-1,那么dp[n][V]=0 } }
四)利用滚动数组做空间上面的优化+直接在原始的代码上稍加修改即可
我们的dp[i][j]所依赖的是两部分的值dp[i][j]所进行依赖的是dp[i-1][j]和dp[i][j-v[i]],当我们在进行填写dp[i][j]的时候,只是需要dp[i-1]和dp[i][j-v[i]]的值即可,至于其他的值我是不会关心的