題目描述
01揹包問題
有 N 件物品和一個容量是 V 的揹包。每件物品只能使用一次。
第 i 件物品的體積是 vi,價值是 wi。
求解將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且總價值最大。
輸出最大價值。
輸入格式
第一行兩個整數,N,V,用空格隔開,分別表示物品數量和揹包容積。
接下來有 N 行,每行兩個整數 vi,wi,用空格隔開,分別表示第 i 件物品的體積和價值。
輸出格式
輸出一個整數,表示最大價值。
數據範圍
0<N,V≤1000
0<vi,wi≤100
0
輸入樣例
4 5
1 2
2 4
3 4
4 5
輸出樣例:
8
解決思路
01揹包問題可以用動態規劃來求解
按照之前文章提到的動態規劃的解題模板:
(1)確定數組元素含義
int dp[][]=new int[N+1][V+1]
dp[i][j] 表示揹包容積爲j數量爲i時最大的價值。
(2) 確定狀態轉移方程
當計算第i個物品時無非兩種情況,第一種是放該物品,第二種是不放該物品。
第一種情況:dp[i][i]=dp[i-1][j]
第二種情況 dp[i][j]=dp[i-1][j-v[i]]+w[i],這種情況的前提條件是j>=v[i]
因此 dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-v[j]]+w[j])
(3)確定初始值
dp[0][0…j]=0, dp[0…i][0]即不放任何物品,體積爲0時,總價值爲0;
因爲java中new出來的數組初始值爲0,因此這裏不需要任何的操作
(4)根據初始值和狀態轉移方程確定數組元素的值
(5)返回結果 dp[N][V]
(6)考慮狀態壓縮
使用二維數組的代碼如下:
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner scanner =new Scanner(System.in);
int N=scanner.nextInt();//N行 物品數量
int V=scanner.nextInt();//揹包容積
int v[]=new int[N+1];
int w[]=new int[N+1];
for(int i=1;i<=N;i++){
v[i]=scanner.nextInt();
w[i]=scanner.nextInt();
}
int dp[][]=new int[N+1][V+1];//狀態數組
dp[0][0]=0;
for(int i=1;i<=N;i++){
for(int j=1;j<=V;j++){
dp[i][j]=dp[i-1][j];
if(j-v[i]>=0){
dp[i][j]=Math.max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
}
}
}
System.out.println(dp[N][V]);
}
}
壓縮爲一維數組與二維數組不同之處在於遍歷的時候需要逆序,是爲了第i輪中不覆蓋到第i-1輪中的結果。可以參考下文:
對使用倒序的一維數組解決0/1揹包問題的理解
壓縮爲意味數組的代碼如下:
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner scanner =new Scanner(System.in);
int N=scanner.nextInt();//N行 物品數量
int V=scanner.nextInt();//揹包容積
int v[]=new int[N+1];
int w[]=new int[N+1];
for(int i=1;i<=N;i++){
v[i]=scanner.nextInt();
w[i]=scanner.nextInt();
}
int dp[]=new int[V+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]);
}
}
System.out.println(dp[V]);
}
}
更進一步,記錄一下中興的在線筆試題計算反應堆的最大能量,該題目是01揹包問題的變形,不再做過多的贅述
public class Test2_01 {
public static void main(String[] args) {
int reactorCap = 100; // 反應堆的容量(V)
int numberOfRadLiquid = 5; // 現有小瓶數量(N)
int criticalMass = 15; // 反應堆的最大臨界質量(M)
int volume[] = { 50, 40, 30, 20, 10 };// 體積
int masses[] = { 1, 2, 3, 9, 5 };// 質量
int energies[] = { 300, 480, 270, 200, 180 }; // 能量
int dp[][][]=new int[numberOfRadLiquid+1][criticalMass+1][reactorCap+1];
for(int i=1;i<=numberOfRadLiquid;i++) {
for(int j=1;j<=criticalMass;j++) {
for(int k=1;k<=reactorCap;k++) {
dp[i][j][k]=dp[i-1][j][k];
if(j>=masses[i-1]&&k>=volume[i-1]) {
dp[i][j][k]=Math.max(dp[i][j][k], dp[i-1][j-masses[i-1]][k-volume[i-1]]+energies[i-1]);
}
}
}
}
System.out.print(dp[numberOfRadLiquid][criticalMass][reactorCap]);
}
}
空間壓縮
public class Test2_02 {
public static void main(String[] args) {
int reactorCap = 100; // 反應堆的容量(V)
int numberOfRadLiquid = 5; // 現有小瓶數量(N)
int criticalMass = 15; // 反應堆的最大臨界質量(M)
int volume[] = { 50, 40, 30, 20, 10 };// 體積
int masses[] = { 1, 2, 3, 9, 5 };// 質量
int energies[] = { 300, 480, 270, 200, 180 }; // 能量
int dp[][]=new int[criticalMass+1][reactorCap+1];
for(int i=1;i<=numberOfRadLiquid;i++) {
for(int j=criticalMass;j>=masses[i-1];j--) {
for(int k=reactorCap;k>=volume[i-1];k--) {
dp[j][k]=Math.max(dp[j][k], dp[j-masses[i-1]][k-volume[i-1]]+energies[i-1]);
}
}
}
System.out.print(dp[criticalMass][reactorCap]);
}
}