题目:
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N行,每行两个整数 vi,wi用空格隔开,分别表示第 ii件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤1000
思路:
f[i][j] 只看前 i 个物品,总体积是j 的情况下,总价值最大是多少
result = max(f [n][0~v])
状态的计算: f [i] [j] = ???
1) 第 i 个物品不选:f [i] [j] = f [i-1] [j];
2) 第 i 个物品选 :f [i][j] = f [ i-1 ] [ j - v[i];
f [i][j] = max(1,2)
初始化:f [0][0] = 0;没有选任何物品,且体积为0;
二维数组解题-代码:
import java.util.Scanner;
public class _01背包 {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
int N=scan.nextInt(); //物品数量
int V=scan.nextInt(); //背包容量
int[][] dp=new int[N+1][V+1]; //初始化二维数组
for(int i=1;i <= N; i++) { // i 表示物品的编号
int v=scan.nextInt(); //物品体积
int w=scan.nextInt(); //物品价值
for(int j=0;j <= V; j++) { // j 表示物品的重量/体积
if(j >= v) { //如果物品的重量小于背包的体积
dp[i][j]=Math.max(dp[i-1][j], dp[i-1][j-v]+w);
}else {
dp[i][j]=dp[i-1][j];
}
}
}
System.out.println(dp[N][V]); //输出最大价值
scan.close();
}
}
详解:
在0-1背包问题中,我们可以定义一个二维数组dp[i][j]
,其中i
表示前i
个物品,j
表示背包容量为j
时的最大价值。数组dp
的初始值都应该是0,因为一开始背包中没有物品,所以价值为0。
接下来,我们遍历每一个物品和每一个背包容量。对于每一个物品,我们有两种选择:放入背包或不放入背包。如果放入背包,我们需要检查背包的剩余容量是否足够。如果足够,我们可以计算放入该物品后的总价值,并与之前的最大价值进行比较,取较大值。如果不放入背包,那么背包中的最大价值就是前一个物品在相同背包容量下的最大价值。
具体的动态规划状态转移方程::dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i])
这个方程的意思是:对于第i
个物品,如果我们选择不放入背包(即dp[i-1][j]
),那么背包中的最大价值就是前i-1
个物品在背包容量为j
时的最大价值;如果我们选择放入背包(即dp[i-1][j-v[i]] + w[i]
),那么背包中的最大价值就是前i-1
个物品在背包容量为j-v[i]
时的最大价值加上当前物品的价值w[i]
。我们取这两种情况中的较大值作为dp[i][j]
的值。
最后,dp[N][V]
就是我们要找的答案,即前N
个物品在背包容量为V
时的最大价值。
一维数组解题-代码
import java.util.Scanner;
public class _01背包_1 {
public static void main(String[] args) {
Scanner scanner =new Scanner(System.in);
int N =scanner.nextInt(); //物品数量
int V = scanner.nextInt(); //背包容量
//初始化一维数组
int[] dp=new int[V+1]; //为什么数组的大小是背包容量加一??
/* 是因为我们想表示从容量0到v的所有可能情况
当我们讨论背包的容量时,通常从0开始计数,来表示没有放入任何物品的情况
如果我们只分配v个空间给dp数组,那么没有空间来表示背包容量为0的情况了
*/
for(int i=1 ; i <= N; i++) {
int v=scanner.nextInt();
int w=scanner.nextInt();
for(int j=V;j>=v;j--) {
dp[j] = Math.max(dp[j], dp[j-v]+w);
}
}
System.out.println(dp[V]);
scanner.close();
}
}
讲解:
for(int j=V;j>=v;j--) {
dp[j] = Math.max(dp[j], dp[j-v]+w);
}
循环变量 j
从 V
(背包的最大容量)开始,递减到v
(当前物品的体积)。这个循环的方向是从大到小,这是为了确保在计算dp[j]
时,dp[j-v]
的值已经更新为放入当前物品之前的最大价值。
在循环体中,我们更新dp[j]
的值。这里使用了Math.max
函数来比较两个值并取较大者。这两个值分别是:
-
dp[j]
:这是当前背包容量为j
时的最大价值。如果不放入当前物品,那么最大价值保持不变。 -
dp[j-v] + w
:这是放入当前物品后的潜在最大价值。dp[j-v]
表示在放入当前物品之前,背包容量为j-v
时的最大价值,而w
是当前物品的价值。将这两者相加,就得到了放入当前物品后的总价值。
Math.max(dp[j], dp[j-v]+w)
会返回这两个值中的较大者,然后赋值给dp[j]
。这样,dp[j]
就存储了在当前背包容量j
下,考虑放入或不放入当前物品后的最大价值。
这个循环对于每个物品都会执行一次,每次都会更新从V
到v
的所有dp[j]
的值。最终,当所有物品都考虑过后,dp[V]
就会存储整个问题的解,即背包容量为V
时的最大价值。