-
问题背景
- 一个容量是V的背包和N件物品,每件物品的体积是v
i,价值是wi,每件物品只能使用一次,求能装走物品的最大价值
- 一个容量是V的背包和N件物品,每件物品的体积是v
-
Dp分析
-
状态表示
- 背包问题一般是用二维的状态表示
f(i,j) f(i, j)表示的是哪一个集合:所有满足如下条件的选法的集合- 只从前
i个物品中选 - 选出来的物品的总体积小于等于
j
- 只从前
f(i, j)存的是什么属性:Max,Min,数量;在这里f(i,j)存的应该是最大值,即所有满足这种选法的最大值
- 背包问题一般是用二维的状态表示
-
状态计算:
f(i,j)可以怎么算出来?- 状态计算一般表示的是集合的划分
- 划分的原则
- 不重:一个元素只能出现在一个子集中(不一定所有情况都要满足,求最大值重复也无所谓)
- 不漏:所有元素都要在子集中出现(任何情况都要满足)
- 划分的原则
- 01背包问题一般将集合划分为两个子集
- 不含
i的选法:从从1 ~ i - 1中选,总体积小于等于j- f(i - 1, j)
- 含
i的选法:从1 ~ i中选,并且i一定要选上,总体积小于等于j- f(i - 1, j - v
i) + wi - 这种选法只有当 j >= v
i时才有意义
- f(i - 1, j - v
- 不含
- 因此 f(i, j) = max( f(i - 1, j), f(i - 1, j - v
i) + wi)
- 状态计算一般表示的是集合的划分
-
代码
-
//初始化f[0][0 ~ m]都是0,所以省略 for (int i = 1; i <= n; i++) { for (int j = 0; j <= m; j++) { //第一个子集 f[i][j] = f[i - 1][j]; //第二个子集 if (j >= V[i]) { f[i][j] = Math.max(f[i][j], f[i - 1][j - V[i]] + W[i]); } } }
-
-
优化:将二维转换成一维
-
观察状态计算的过程,可以发现两个特点:
- f( i ) 层的计算只用到了 **f(i - 1)**层
- f( i ) 层中的 j 的计算只用到了**f(i - 1)**层中的 j 和 j - v
i
-
因此可以做如下优化
- 将 i 这一维直接优化掉,i 的每一层循环中的 j 都在原先的 j 上进行覆盖
- j 的每一层循环中要注意 ,在计算 j 时要用到当前上一层 j和 j - v
i,由于j - vi< j,所以在这一层循环中要从后往前遍历,防止 j - vi被提前更新
-
代码
-
//初始化f[0][0 ~ m]都是0,所以省略 for (int i = 1; i <= n; i++) { for (int j = m; j >= V[i]; j--) { f[j] = Math.max(f[j], f[j - V[i]] + W[i]); } }
-
-
01 01背包问题
- 题目
- 题解1
import java.io.*;
import java.util.*;
public class Main {
public static final int N = 1010;
public static int[] V = new int[N];
public static int[] W = new int[N];
//只从前i个物品中选,选出来的物品的总体积小于等于j,满足这两个条件的价值的最大值
public static int[][] f = new int[N][N];
public static int n, m;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
String[] str1 = br.readLine().split(" ");
n = Integer.parseInt(str1[0]);
m = Integer.parseInt(str1[1]);
for (int i = 1; i <= n; i++) {
String[] str2 = br.readLine().split(" ");
V[i] = Integer.parseInt(str2[0]);
W[i] = Integer.parseInt(str2[1]);
}
//初始化f[0][0 ~ m]都是0,所以省略
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
//第一个子集
f[i][j] = f[i - 1][j];
//第二个子集
if (j >= V[i]) {
f[i][j] = Math.max(f[i][j], f[i - 1][j - V[i]] + W[i]);
}
}
}
pw.println(f[n][m]);
br.close();
pw.close();
}
}
- 题解2
import java.io.*;
import java.util.*;
public class Main {
public static final int N = 1010;
public static int[] V = new int[N];
public static int[] W = new int[N];
//只从前i个物品中选,选出来的物品的总体积小于等于j,满足这两个条件的价值的最大值
public static int[] f = new int[N];
public static int n, m;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
String[] str1 = br.readLine().split(" ");
n = Integer.parseInt(str1[0]);
m = Integer.parseInt(str1[1]);
for (int i = 1; i <= n; i++) {
String[] str2 = br.readLine().split(" ");
V[i] = Integer.parseInt(str2[0]);
W[i] = Integer.parseInt(str2[1]);
}
//初始化f[0][0 ~ m]都是0,所以省略
for (int i = 1; i <= n; i++) {
for (int j = m; j >= V[i]; j--) {
f[j] = Math.max(f[j], f[j - V[i]] + W[i]);
}
}
pw.println(f[m]);
br.close();
pw.close();
}
}