题目描述
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V (0<N≤1000, 0<V≤20000),用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤20000
0<vi,wi,si≤20000
样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
10
回顾
(暴力枚举)
对每一种的物品采用分组背包,只能选择1v, 2v,…,kv中的一个
时间复杂度 O(NVS)
Java 代码
import java.util.*;
public class Main {
public static int backPack3(int N, int V, int[] v, int[] w, int[] s) {
int[] dp = new int[V + 1];
for (int i = 0; i < N; i++) {
// this is wrong, in this case we are acturely doing 0-1 backpack using 0v[i], 1v[i]...s[i]v[i]
// for (int k = 0; k <= s[i]; k++) {
// for (int j = V; j >= k * v[i]; j--) {
// dp[j] = Math.max(dp[j], dp[j - k * v[i]] + k * w[i]);
// }
// }
// this is correct, only 1 case for k * v[i]
for (int j = V; j >= 0; j--) {
for (int k = 0; k <= s[i] && k * v[i] <= j; k++) {
dp[j] = Math.max(dp[j], dp[j - k * v[i]] + k * w[i]);
}
}
}
return dp[V];
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int V = sc.nextInt();
int[] v = new int[N];
int[] w = new int[N];
int[] s = new int[N];
for (int i = 0; i < N; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
s[i] = sc.nextInt();
}
System.out.println(backPack3(N, V, v, w, s));
}
}
算法1
(二进制划分)
对每一种的物品数量二进制划分:1v, 2v, 4v, …
时间复杂度 O(NVlog(S))
Java 代码
import java.util.*;
public class Main {
public static int backPack4(int V, List<Integer> vList, List<Integer> wList) {
int[] dp = new int[V + 1];
for (int i = 0; i < vList.size(); i++) {
for (int j = V; j >= vList.get(i); j--) {
dp[j] = Math.max(dp[j], dp[j - vList.get(i)] + wList.get(i));
}
}
return dp[V];
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int V = sc.nextInt();
List<Integer> vList = new ArrayList<>();
List<Integer> wList = new ArrayList<>();
for (int i = 0; i < N; i++) {
int v = sc.nextInt();
int w = sc.nextInt();
int s = sc.nextInt();
int mask = 1;
while (s >= mask) {
vList.add(mask * v);
wList.add(mask * w);
s -= mask;
mask *= 2;
}
if (s > 0) {
vList.add(s * v);
wList.add(s * w);
}
}
System.out.println(backPack4(V, vList, wList));
}
}
算法2
(单调队列)
对每一种的物品,设f(i) = dp[j - i * v] + i * w, dp[j] = max(f(1), f(2), …, f(k));
对f序列用单调队列优化
时间复杂度 O(N*V)
Java 代码
import java.util.*;
/*
*1. for an item with v and w to fill package j, only consider the cases:
* dp[j - v] + w, dp[j - 2 * v] + 2 * w, ... , dp[j - k * v] + k * w (k = min(s[i], j / v));
*2. f(i) = dp[j - i * v] + i * w, we want to get f(1), f(2), ... ,f(k) dynamicaly, could use Monotone Queue;
*3. time complexity: O(N * V)
*/
public class Main {
public static int backPack5(int N, int V, int[] v, int[] w, int[] s) {
int[] dp = new int[V + 1];
int[] copy = new int[V + 1];
int[] queue = new int[V + 1];
for (int i = 0; i < N; i++) {
// for index i, copy arr stores the result only using item before i
for (int c = 0; c <= V; c++) copy[c] = dp[c];
// status only transfer amone mode v has same result
for (int j = 0; j < v[i]; j++) {
// use 2 pointer to simulat monotone queue
int head = 0;
int tail = -1;
for (int k = j; k <= V; k += v[i]) {
if (head <= tail && (k - queue[head]) / v[i] > s[i]) head++;
if (head <= tail) dp[k] = Math.max(dp[k], copy[queue[head]] + (k - queue[head]) / v[i] * w[i]);
while (head <= tail && copy[k] >= copy[queue[tail]] + (k - queue[tail]) / v[i] * w[i]) tail--;
queue[++tail] = k;
}
}
}
return dp[V];
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int V = sc.nextInt();
int[] v = new int[N];
int[] w = new int[N];
int[] s = new int[N];
for (int i = 0; i < N; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
s[i] = sc.nextInt();
}
System.out.println(backPack5(N, V, v, w, s));
}
}