一、01背包问题
你有一个背包,最多能容纳的体积是V。
现在有n个物品,第i个物品的体积为vi ,价值为wi。
(1)求这个背包至多能装多大价值的物品?
(2)若背包恰好装满,求至多能装多大价值的物品?
输入描述:
第一行两个整数n和V,表示物品个数和背包体积。
接下来n行,每行两个数vi和wi,表示第i个物品的体积和价值。
1≤n,V,vi,wi≤10001≤n,V,vi,wi≤1000
输出描述:
输出有两行,第一行输出第一问的答案,第二行输出第二问的答案,如果无解请输出0。
二维dp解法:
解题思路:动态规划
定义一个二维dp数组,dp[i][j]的值表示当前背包内最大的价值(重量),i表示考虑装入的物品,j表示背包内还剩余容量/体积。初始化为dp[0][0]=0,因为此时没有考虑物品,并且背包不存在容量,因此值为0;
对于每个物品i,我们有两种选择:放入背包,或者不放入背包。
1、如果不放入背包,那么dp[i][j]=dp[i-1][j]。这代码意思是对于现在这件物品i,我们不存入背包中,那么背包剩余的容量还是j,背包包含的价值也同样没有改变。
2、如果放入背包,那么我们需要考虑当前背包的容量j是否可以装下这件物品的体积v[i]。
综合上述两种情况,可以得出
状态转移方程:
j<v[i]:dp[i][j] = dp[i-1][j];
j>=v[i]: dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
遍历过程:
初始化背包dp[0[[0]=0,我们将背包容量从0到V进行遍历,判断是否能够存在每个不同的物品,最后在比较最大值即可。
public static int knapsack (int V, int n, int[]v,int[]w) {
int[][] dp = new int[n+1][V+1];// i表示装入的物品个数,j表示当前背包容量(体积)
//dp表示的意思 是 n个物品 背包体积 V 能装的重量是dp[i][j]
dp[0][0] = 0;
for (int i = 1; i <=n ; i++) {
for (int j = 0; j <=V ; j++) {
if (j<v[i]){ //背包可用容量小于物品的 容量,无法存入
dp[i][j] = dp[i-1][j];
}
else {
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
}
}
}
return dp[n][V];
}
一维dp解法:
解题思路:
定义dp[j]表示背包容量为j时,能装入的最大价值。
对于每个物品i,考虑是否放入背包中。若不放入,则总价值不变;
若放入,则总价值为f[j-v[i]] + w[i](在当前容量下,放入物品i后的总价值)。
状态转移方程:
dp[j] = max(dp[j], dp[j-v[i]] + w[i])。
区别:
一维dp解法和二维dp解法区别在于我们遍历物品的时候需要从最大的背包容量开始遍历。如果我们从最小背包容量开始考虑,那么在更新较大的背包容量 j
时,较小的背包容量 j-v[i]
可能已经考虑过了物品 i
。这会导致物品 i
被错误地计算两次,即它在更新 dp[j-v[i]]
时被考虑过一次,在更新 dp[j]
时又被考虑。而从大容量开始遍历的话就不会存在这种问题
// 一维dp
public static int knapsack1 (int V, int n, int[]v,int[]w) {
int[]dp = new int[n+1];
for (int i = 1; i <=n ; i++) {
for (int j = V; j >=v[i] ; j++) {
dp[j]=Math.max(dp[j], dp[j-v[i]]+w[i]);
}
}
return dp[V];
}
注:对于该题的第二小问,思路是相同的。只需要先初始化整个背包的价值为最小数,依据同样的状态转移方程进行遍历,最后判断这个背包dp[v]是否小于0即可判断是否装满。
int[] dp2=new int[V+1];
Arrays.fill(dp2,Integer.MIN_VALUE);
//没有物品时,价值为0
dp2[0]=0;
for(int i=1;i<=n;i++){
//由于每个物品只能用一次,为了防止重复计算,需要倒序遍历
for(int j=V;j>=v[i];j--){
//状态转移,要么选择第i件物品,要么不选,取价值最大的
dp2[j]=Math.max(dp2[j-v[i]]+w[i],dp2[j]);
}
}
//如果小于0,说明不能从初始状态转移过来,无解
if(dp2[V]<0){
dp2[V]=0;
}
System.out.println(dp2[V]);
二、完全背包问题
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
输入格式:
第一行两个整数,N 和 V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi, wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式:
输出一个整数,表示最大价值。
数据范围:
0 < N, V ≤ 1000
0 < vi, wi ≤ 1000
二维dp解法:
定义二维dp数组,dp[i][j]的含义为:i表示当前考虑装入的物品,j表示背包当前的容量(体积),dp[i][j]的值表示当前背包所含有的价值(重量)。
解题思路与前文01背包是相同的,不过完全背包问题中的 物品不是只有一件,可以多次存储,因此需要多加一重循环,来表示存入该件物品i的数量k。
状态转移方程:
dp[i][j]=max(dp[i-1][j], dp[i-1][j-v[i]*k]+w[i]*k);
循环遍历:
· 外层循环遍历所有物品,中层循环遍历所有可能的背包容量。
· 内层循环遍历每个物品的可能选取次数k(即物品i可以被选取0次、1次、2次,直到k * v[i]超过当前背包容量j)。
· 对于每种情况,更新f[i][j]为max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k),表示考虑选取k次物品i时的最大价值。
public static int knapsack (int V, int n, int[]v,int[]w) {
int[][] dp = new int[n+1][V+1];// i表示装入的物品个数,j表示当前背包容量(体积)
//dp表示的意思 是 n个物品 背包体积 V 能装的重量是dp[i][j]
dp[0][0] = 0;
for(int i=1;i<=n;i++){
for(int j=0;j<=V;j++){
for(int k=0;k*v[i]<=V;k++){
dp[i][j]=Math.max(dp[i-1][j], dp[i-1][j-v[i]*k]+w[i]*k);
}
}
}
return dp[n][V];
}
一维dp解法:
与解决01背包问题的一维dp解法类似,但是区别在于完全背包问题运行同一物品多次存储,那么就不需要像01背包问题一样进行倒序遍历。直接利用正序遍历的方式,就可以直接将多次存储的问题考虑进去。
状态转移方程:
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
public static int knapsack (int V, int n, int[]v,int[]w) {
int[][] dp = new int[n+1][V+1];// i表示装入的物品个数,j表示当前背包容量(体积)
//dp表示的意思 是 n个物品 背包体积 V 能装的重量是dp[i][j]
dp[0][0] = 0;
for(int i=1;i<=n;i++){
for(int j=v[i];j<=V;j++){ //这里让j从v[i]开始是为了保证每次开始都是还没有存入这件物品,从0开始也是可以的
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
return dp[V];
}
三、多重背包
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
输入格式:
第一行两个整数,N 和 V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi, wi, si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式:
输出一个整数,表示最大价值。
数据范围:
0 < N, V ≤ 100
0 < vi, wi, si ≤ 100
解题思路:
完全背包问题是物品有无限多个,而多重背包问题是每个物品i存在一定的数量s[i]。因此做法可以参考完全背包的三重循环做法,只需要将k的范围修改为k<=s[i]。
状态转移方程:
dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+w[i]*k ); k=0,1,2,3……s[i]
public static int knapsack (int V, int n, int[]v,int[]w,int[]s) {
int[][] dp = new int[n+1][V+1];// i表示装入的物品个数,j表示当前背包容量(体积)
//dp表示的意思 是 n个物品 背包体积 V 能装的重量是dp[i][j]
dp[0][0] = 0;
for(int i=1;i<=n;i++){
for(int j=0;j<=V;j++){
for(int k=0;k<=s[i] && k*v[i]<=j;k++){
dp[i][j]=Math.max(dp[i][j],dp[i-1][j-k*v[i]]+w[i]*k );
}
}
}
return dp[n][V];
}