01 背包

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 $v_i$,价值是 $w_i$。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 $v_i,w_i$,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
$0< N,V ≤ 1000$
$0< v_i,w_i ≤ 1000$

输入样例

1
2
3
4
5
4 5
1 2
2 4
3 4
4 5

输出样例:

1
8

思路+方法

f[i][j]表示面对第 i 件物品时,体积为 j 的背包的最大总价值。
两种选择:1. 不放入第 i 件物品。 2. 放入第 i 件物品。

状态转移方程:f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i])

优化:f[j]=max(f[j],f[j−w[i]]+v[i])。此时 j 要从大到小遍历,保证第 i 件物品只能选择一次。否则 f[i][j] 会由 f[i][j−w[i]]+v[i]决定,与题意不符,而顺序遍历却是完全背包的解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Scanner;

public class Main{
public static void main(String[] args) throws Exception{
Scanner reader = new Scanner(System.in);
int N = reader.nextInt();
int V = reader.nextInt();
int[] v = new int[N];
int[] w = new int[N];
for(int i=0; i<N; i++){
v[i] = reader.nextInt();
w[i] = reader.nextInt();
}
reader.close();
int[] dp = new int[V+1];
for(int i=0; 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]);
}
}

时间复杂度 O(VN)

完全背包

完全背包题目与01背包大体相似,只不过每件物品可以无限选择。

思路 + 代码

状态转移方程:f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Scanner;

class Main{
public static void main(String[] args) throws Exception{
Scanner in = new Scanner(System.in);
int N = in.nextInt();
int V = in.nextInt();
int[] w = new int[N];
int[] v = new int[N];
for(int i=0; i<N; i++){
v[i] = in.nextInt();
w[i] = in.nextInt();
}
in.close();
int[] dp = new int[V+1];
for(int i=0; i<N; i++){
for(int j=v[i]; j<=V; j++){
dp[j] = Math.max(dp[j], dp[j-v[i]]+w[i]);
}
}
System.out.println(dp[V]);
}
}

时间复杂度 O(VN)

多重背包

多重背包是每个物品指定了数量。

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 $s_i$ 件,每件体积是 $v_i$,价值是 $w_i$。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 $v_i,w_i,s_i$,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
$0<v_i,w_i,s_i≤100$

输入样例

1
2
3
4
5
4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

1
10

思路 + 代码

将多重背包转化为 01背包问题,即将有限量的物品划分为互相独立的部分,每个独立的部分物品可以看作01背包问题,继续采用01背包的思想解决。

状态转移方程:f[i][j]=max(f[i−1][j],f[i−1][j−k*w[i]]+k*v[i])

将第 i 种物品转化为 p[i]件物品,每件物品的系数分别为 $1,2, 4, …, 2^{k-1}, p[i]-2^k+1 > 0$, k是满足 $p[i]-2^k+1 > 0$ 的最大整数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.Scanner;

class Main{
public static void main(String[] args) throws Exception{
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();
}
sc.close();
int[]dp = new int[V+1];
for(int i=0; i<N; i++){
int num = Math.min(s[i], V/v[i]);
for(int k=1; num>0; k <<= 1){
if(k>num) k = num;
num -= k;
for(int j=V; j>=k*v[i]; j--){
dp[j] = Math.max(dp[j], dp[j-k*v[i]]+k*w[i]);
}
}
}
System.out.println(dp[V]);
}
}

时间复杂度 O(V$\sum{log(p(i))}$)

混合背包

混合背包其实是 01背包、多重背包和完全背包的混合体。

有 N 种物品和一个容量是 V 的背包。

物品一共有三类:

第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 $s_i$ 次(多重背包);
每种体积是 $v_i$,价值是 $w_i$。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 $v_i,w_i,s_i$,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

$s_i$=−1 表示第 i 种物品只能用1次;
$s_i$=0 表示第 i 种物品可以用无限次;
$s_i$>0 表示第 i 种物品可以使用 $s_i$ 次;

输出格式
输出一个整数,表示最大价值。

数据范围
$ 0<N,V≤1000 $
$ 0<v_i,w_i≤1000 $
$ −1≤s_i≤1000 $

输入样例

1
2
3
4
5
4 5
1 2 -1
2 4 1
3 4 0
4 5 2

输出样例:
1
8

思路 + 代码

加入if-else判断。

1
2
3
4
5
6
7
8
9
// p[i]:每个物品的件数,0代表无穷个
for (int i = 1; i <= n; i++)
if (p[i] == 0)
for (int j = w[i]; j <= V; j++)
f[j] = max(f[j], f[j - w[i]] + v[i]);
else
for (int k = 1; k <= p[i]; k++)
for (int j = V; j >= w[i]; j--)
f[j] = max(f[j], f[j - w[i]] + v[i]);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.Scanner;

class Main{
public static void main(String[] args) throws Exception{
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();
}
sc.close();
int[] dp = new int[V+1];
for(int i=0; i<N; i++){
if(s[i] == -1){
for(int j=V; j>=v[i]; j--){
dp[j] = Math.max(dp[j], dp[j-v[i]]+w[i]);
}
}else if(s[i]==0){
for(int j=v[i]; j<=V; j++){
dp[j] = Math.max(dp[j], dp[j-v[i]]+w[i]);
}
}else{
int num = Math.min(s[i], V/v[i]);
for(int k=1; num>0; k <<=1){
if(k>num) k=num;
num -= k;
for(int j=V; j>=k*v[i]; j--){
dp[j] = Math.max(dp[j], dp[j-k*v[i]]+k*w[i]);
}
}
}
}
System.out.println(dp[V]);
}
}

二维背包

物品的约束条件除了体积外,增加了重量一维,其余跟01背包一样。

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。

每件物品只能用一次。体积是 $v_i$,重量是 $m_i$,价值是 $w_i$。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。

接下来有 N 行,每行三个整数 $v_i,m_i,w_i$,用空格隔开,分别表示第 i 件物品的体积、重量和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
$0<N≤1000$
$0<V,M≤100$
$0<v_i,m_i≤100$
$0<w_i≤1000$

输入样例

1
2
3
4
5
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6

输出样例:

1
8

思路 + 代码

跟01背包类似,只不过两个状态。

状态转移方程:f[j][k]=max(f[j][k],f[j−w[i]][k-m[i]]+k*v[i])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.Scanner;

class Main{
public static void main(String[] args) throws Exception{
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int V = sc.nextInt();
int M = sc.nextInt();
int[] v = new int[N];
int[] m = new int[N];
int[] w = new int[N];
for(int i=0; i<N; i++){
v[i] = sc.nextInt();
m[i] = sc.nextInt();
w[i] = sc.nextInt();
}
sc.close();
int[][] dp = new int[V+1][M+1];
for(int i=0; i<N; i++){
for(int j=V; j>=v[i]; j--){
for(int k=M; k>=m[i]; k--){
dp[j][k] = Math.max(dp[j][k], dp[j-v[i]][k-m[i]]+w[i]);
}
}
}
System.out.println(dp[V][M]);
}
}

分组背包

在01背包的基础上,对不同物品进行了分组,每组只能选取一件物品。

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 $v_ij$,价值是 $w_ij$,其中 i 是组号,j 是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。

接下来有 N 组数据:

每组数据第一行有一个整数 $S_i$,表示第 i 个物品组的物品数量;
每组数据接下来有 $S_i$ 行,每行有两个整数 $v_ij,w_ij$,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;

输出格式
输出一个整数,表示最大价值。

数据范围
$0<N,V≤100$
$0<S_i≤100$
$0<v_ij,w_ij≤100$

输入样例

1
2
3
4
5
6
7
8
3 5
2
1 2
2 4
1
3 4
1
4 5

输出样例:

1
8

思路 + 代码

状态转移方程:

1
2
3
4
for i in (每一种分组):
for j in range(V,0,-1):
for k in 分组[i]:
dp[j] = max(dp[j], dp[j-分组[i][0]]+分组[i][1])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.util.Scanner;
import java.util.HashMap;
import java.util.ArrayList;

class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int K = sc.nextInt();
int V = sc.nextInt();
int N = 0;
HashMap<Integer, ArrayList<int[]>> map = new HashMap<>();
for(int i=0; i<K; i++){
int s = sc.nextInt();
N += s;
ArrayList<int[]> arr = new ArrayList<>();
for(int j=0; j<s;j++){
int[] tmp = new int[2];
tmp[0] = sc.nextInt();
tmp[1] = sc.nextInt();
arr.add(tmp);
map.put(i, arr);
}
}
sc.close();
int[] dp = new int[V+1];
for(int i=0; i<K; i++){
ArrayList<int[]> arr = map.get(i);
for(int j=V; j>=0; j--){
for(int k=0; k<arr.size();k++){
if(j>=arr.get(k)[0])
dp[j] = Math.max(dp[j], dp[j-arr.get(k)[0]]+arr.get(k)[1]);
}
}
}
System.out.println(dp[V]);
}
}