01背包指在背包容量确定的情况下,有不同种类的商品,商品的价值和重量各不相同,问怎么拿商品能使拿出的商品总价值最大,商品总重量不得超过背包容量。
该题使用动态规划求解,定义w[i]为i商品的重量,v[i]为i商品的价值。定义dp[i][j] 用于保存在背包容量为j时,前i个商品中所能拿到的最优解,即总价值最大解。
在这里我们不考虑商品i之后的商品是否获取(商品排列顺序随意,最后都会遍历到)。则只有两个判断条件,即第i个商品拿还是不拿?
第一:如果当前背包容量j小于i商品的重量w[i],那肯定是拿不了的,所以,这时的dp[i][j]与i商品无关,其只能等于当背包重量为j时,前i-1商品中的最优解,即公式为dp[i][j] = dp[i-1][j]。
第二:(1)如果当前背包容量j >= i商品的重量w[i],那可以考虑是否拿i商品,如果拿,则背包的剩余容量就是j-w[i],然后再用这剩余的容量去装前i-1种商品,则可知这样的最有解应为dp[i][j] = dp[i-1][j-w[i]] + v[i]。
(2)如果不拿i商品,则和一情况一样,dp[i][j] = dp[i-1][j]。
所以在当前背包容量j >= i商品的重量w[i]时只有这两种分支,取其中最大值,即dp[i][j] = Math.max(dp[i-1][j-w[i]] + v[i],dp[i][j] = dp[i-1][j])。
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);// 假定是6个物品
int[] goodsworth = new int[6];
int[] bagweigt = new int[6];
for (int i = 0; i < 6 ; i++) {
goodsworth[i] = sc.nextInt();
bagweigt[i] = sc.nextInt();
}
int[][] dp = new int[7][9]; // 定义背包容量为8
for (int i = 1; i < 7; i++) {
for (int j = 0; j < 9; j++) {
if(bagweigt[i-1] > j){
dp[i][j] = dp[i-1][j]; //背包容量不足,拿不了i商品
}else {
dp[i][j] = Math.max(dp[i-1][j-bagweigt[i-1]]+goodsworth[i-1],dp[i-1][j]); //取拿i商品和不拿i商品两种情况的最大值。
}
}
}
System.out.println(dp[6][8]);
}
}
关于01背包问题,牛客网有一道变形题:
本题和01背包问题差别不大,无非是增加了从属关系,从属关系中主物品最多有2个附属物品,则变化的情况变为了,在拿主物品时,判断是否要拿从属物品,即4中情况:(1)只拿主物品 ;(2)拿主物品和副物品1 ;(3)拿主物品和副物品2 ;(4)拿主物品和副物品1和副物品2 ;
在这4个中寻找最大值,动态规划过程仍和上述一样,如果拿则dp[i][j] = Math.max(dp[i-1][j-w[i]] + v[i],dp[i][j] = dp[i-1][j])。不拿则dp[i][j] = dp[i-1][j]。代码如下
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String[] str = sc.nextLine().split(" ");
int money = Integer.parseInt(str[0]);
int count = Integer.parseInt(str[1]);
if(count<=0||money<=0) System.out.println(0);
goods[] gd = new goods[count+1];
for(int i = 1; i <= count; i++){
int v = sc.nextInt();
int p = sc.nextInt();
int q = sc.nextInt();
gd[i] = new goods(v, p, q);
}
for(int i = 1; i <= count; i++){ //这段不能和上面合并,如果合并,在副商品添加时若主物品没有添加,则会报空指针异常。因为此时主物品位置处没有对象,值为null。
if(gd[i].q > 0){
if(gd[gd[i].q].a1 == 0)
gd[gd[i].q].setA1(i);
else{
gd[gd[i].q].setA2(i);
}
}
}
int[][] dp = new int[count+1][money+1];
for (int i = 1; i <= count; i++) {
int v=0,v1=0,v2=0,v3=0,tempdp=0,tempdp1=0,tempdp2=0,tempdp3=0;
v = gd[i].v;
tempdp = gd[i].p*v; //只有主件
if(gd[i].a1!=0){//主件加附件1
v1 = gd[gd[i].a1].v+v;
tempdp1 = tempdp + gd[gd[i].a1].v*gd[gd[i].a1].p;
}
if(gd[i].a2!=0){//主件加附件2
v2 = gd[gd[i].a2].v+v;
tempdp2 = tempdp + gd[gd[i].a2].v*gd[gd[i].a2].p;
}
if(gd[i].a1!=0&&gd[i].a2!=0){//主件加附件1和附件2
v3 = gd[gd[i].a1].v+gd[gd[i].a2].v+v;
tempdp3 = tempdp + gd[gd[i].a1].v*gd[gd[i].a1].p + gd[gd[i].a2].v*gd[gd[i].a2].p;
}
for(int j=1; j<=money; j++){
if(gd[i].q > 0) { //当物品i是附件时,相当于跳过
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = dp[i-1][j]; //为保证dp[i][j]一定有值而不是默认的0.
if(j>=v&&v!=0) dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-v]+tempdp);
if(j>=v1&&v1!=0) dp[i][j] = Math.max(dp[i][j],dp[i-1][j-v1]+tempdp1);
if(j>=v2&&v2!=0) dp[i][j] = Math.max(dp[i][j],dp[i-1][j-v2]+tempdp2);
if(j>=v3&&v3!=0) dp[i][j] = Math.max(dp[i][j],dp[i-1][j-v3]+tempdp3);
}
}
}
System.out.println(dp[count][money]);
}
private static class goods{
public int v;//物品的价格
public int p;//物品的重要度
public int q;//物品的主附件ID
public int a1 = 0;//附件1ID
public int a2 = 0;//附件2ID
public goods(int v, int p, int q){
this.v = v;
this.p = p;
this.q = q;
}
public void setA1(int a1){
this.a1 = a1;
}
public void setA2(int a2){
this.a2 = a2;
}
}
}