常用模型:背包
背包模型 | 区别 | 状态方程 |
---|---|---|
01背包 | 每件物品最多只能用一次 | f[i][j] = max(f[i-1, j], f[i-1, j-v] + w) |
完全背包 | 每件物品可以用无限次 | f[i][j] = max(f[i-1, j], f[i, j-v] + w) |
多重背包 | 优化问题 | |
分组背包 | n组物品,每组m种类型,只能从每组中选一个 |
1.0 1背包——每件物品最多只能用一次
问题:有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。
状态方程:f[i][j] = Math.max(f[i-1][j], f[i-1][j-v[i]] + w[i])
import java.io.IOException;
import java.util.Scanner;
/**
* @author mys
* @date 2022/2/1 20:29
* 01背包
* 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。
* 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。
*/
public class p2_dp_01bag {
static int N = 1010;
public static void main(String[] args) throws IOException {
int[] v = new int[N], w = new int[N];//v表示物品体积,w表示物品价值
int[][] f = new int[N][N];//f表示状态 i物品个数 j背包容量
Scanner sc = new Scanner(System.in);
System.out.println("请输入物品个数和背包容量:");
int n = sc.nextInt();n表示物品个数,m表示背包容量
int m = sc.nextInt();
//输入所有物品
System.out.println("请输入所有物品体积和价值:");
for (int i = 1; i <= n; i ++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
//初始化时需要枚举所有状态f[0-n][0-m]
//f[0][0-m] = 0:表示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];//不含i
//包含i,注意判断情况,如果j<v[i]为空集,不满足条件
if (j >= v[i]) {
f[i][j] = Math.max(f[i][j], f[i-1][j-v[i]] + w[i]);//先去掉i情况,再加上i的价值
}
}
}
System.out.println(f[n][m]);
}
}
滚动数组:每次都是用固定的几个存储空间,将二维数组转为一维数组,节省存储空间,达到优化的效果。滚动数组只适用于计算最终结果,而不需要存储中间结果的场景
将状态f [i] [j]编程一维f[i]进行优化
为什么一维情况下枚举背包容量需要逆序?
在二维情况下,状态f [i] [j]是由上一轮i - 1的状态得来的,f [i] [j]与f[i - 1] [j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。
状态方程变为:f[j] = max(f[j], f[j - v[i]] + w[i]
最终代码:
import java.io.IOException;
import java.util.Scanner;
/**
* @author mys
* @date 2022/2/1 20:29
* 01背包
* 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。
* 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。
*/
public class p2_dp_01bag2 {
static int N = 1010;
public static void main(String[] args) throws IOException {
int[] v = new int[N], w = new int[N];//v表示物品体积,w表示物品价值
int[] f = new int[N];//f表示状态 i物品个数 j背包容量
Scanner sc = new Scanner(System.in);
System.out.println("请输入物品个数和背包容量:");
int n = sc.nextInt();n表示物品个数,m表示背包容量
int m = sc.nextInt();
//输入所有物品
System.out.println("请输入所有物品体积和价值:");
for (int i = 1; i <= n; i ++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
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]);
}
}
System.out.println(f[m]);
}
}
2.完全背包——每件物品可以用无限次
问题:有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用,第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
/**
* @author mys
* @date 2022/2/18 17:18
* 有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
* 第 i 种物品的体积是 vi,价值是 wi。
* 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
*
* 输入:
* 4 5
* 1 2
* 2 4
* 3 4
* 4 5
* 输出:10
*/
public class p3_dp_completeBag {
static int N = 1010;
public static void main(String[] args) throws IOException {
int[] v = new int[N], w = new int[N];//v:体积 w:价值
int[][] f = new int[N][N];//f:状态
Scanner sc = new Scanner(System.in);
//输入物品个数和背包数量
int n = sc.nextInt();
int m = sc.nextInt();
//输入所有物品体积和价值
for (int i = 1; i <= n; i ++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
//完全背包状态方程 i物品个数 j背包容量
for (int i = 1; i <= n; i ++) {
for (int j = 0; j <= m; j ++) {
//k * v[i] <= j:如果有k个物品i,保证这k个物品的体积小于背包容量
for (int k = 0; k * v[i] <= j; k ++) {
f[i][j] = Math.max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
}
}
}
System.out.println(f[n][m]);
}
}
优化问题
1、优化k层循环
优化后核心代码:
//完全背包
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][j - v[i]] + w[i]);
}
}
}
//对比01背包
for (int i = 1; i <= n; i ++) {
for (int j = 0; j <= m; j ++) {
f[i][j] = f[i - 1][j];//包含i
//不包含i,注意判断情况,如果j<v[i]为空集,不满足条件
if (j >= v[i]) {
f[i][j] = Math.max(f[i][j], f[i-1][j-v[i]] + w[i]);//先去掉i情况,再加上i的价值
}
}
}
2、二维转一维
优化后核心代码:
//完全背包
for (int i = 1; i <= n; i++) {
for (int j = v[i]; j <= m; j++) {
f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
}
}
//对比01背包
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背包非常相似
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包
f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题
最终代码:
import java.io.IOException;
import java.util.Scanner;
/**
* @author mys
* @date 2022/2/18 17:18
* 优化 二维->一维
*/
public class p3_dp_completeBag3 {
static int N = 1010;
public static void main(String[] args) throws IOException {
int[] v = new int[N], w = new int[N];//v:体积 w:价值
int[] f = new int[N];//f:状态
Scanner sc = new Scanner(System.in);
//输入物品个数和背包数量
int n = sc.nextInt();
int m = sc.nextInt();
//输入所有物品体积和价值
for (int i = 1; i <= n; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
//完全背包状态方程 i物品个数 j背包容量
for (int i = 1; i <= n; i++) {
for (int j = v[i]; j <= m; j++) {
f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
}
}
System.out.println(f[m]);
}
}
3.多重背包——优化问题
问题:有 N 种物品和一个容量是 V 的背包,第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大,输出最大价值。
数据范围:
第一种:0<N,V≤1000<N,V≤100, 0<vi,wi,si≤100
第二种:
0<N≤10000<N≤1000
0<V≤20000<V≤2000
0<vi,wi,si≤2000
第一种:暴力解法
import java.util.Scanner;
/**
* @author mys
* @date 2022/2/18 20:23
* 有 N 种物品和一个容量是 V 的背包。
* 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
* 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
*
* 输入:
4 5
1 2 3
2 4 1
3 4 3
4 5 2
* 输出:10
*/
public class p4_dp_multipleBag {
public static int N = 110;
public static void main(String[] args) {
int[] v = new int[N], w = new int[N], s = new int[N];//体积 价值
int[][] f = new int[N][N];//状态
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();//物品个数
int m = sc.nextInt();//背包容量
//输入物品信息
for (int i = 1; i <= n; i ++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
s[i] = sc.nextInt();
}
for (int i = 1; i <= n; i ++) {
for (int j = 0; j <= m; j ++) {
for (int k = 0; k <= s[i] && k * v[i] <= j; k ++) {
//朴素状态方程
f[i][j] = Math.max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
}
}
}
System.out.println(f[n][m]);
}
}
第二种:二进制优化
最终代码:
import java.util.Scanner;
public class p4_dp_multipleBag2 {
//N >= 2000 * log2000 保险取值取25000 2000种物品,每种物品logS件
public static int N = 25000, M = 2010;
public static void main(String[] args) {
int[] v = new int[N], w = new int[N];//体积 价值
int[] f = new int[N];//状态
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();//物品种数
int m = sc.nextInt();//背包容量
int count = 0;//存储新的物品
for (int i = 1; i <= n; i ++) {
//输入当前物品的信息
int volume = sc.nextInt();//体积
int value = sc.nextInt();//价值
int s = sc.nextInt();//个数
//一组二进制数:1 2 4 8 ... 2^k C
int k = 1;//从1开始凑和
//用一组二进制数凑和,只要总和<=S就继续凑和,当总和要>S时就停止,最后如果有剩余用C来凑
while (k <= s) {
count ++;
v[count] = volume * k;//k个物品的体积打包放一起
w[count] = value * k;//k个物品的价值打包放一起
s -= k;//S凑完之后减去
k *= 2;//k变为下一个二进制数
}
//说明还剩下一些,相当于数组中的C 拼凑到一起补上
if (s > 0) {
count ++;
v[count] = volume * s;
w[count] = value * s;
}
}
n = count;//更新n
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]);
}
}
System.out.println(f[m]);
}
}
4.分组背包
n组物品,每组m种类型,只能从每组中选一个
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,VN,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 NN 组数据:
- 每组数据第一行有一个整数 Si,表示第 ii 个物品组的物品数量;
- 每组数据接下来有 Si 行,每行有两个整数 vij,wijvij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000<N,V≤100
0<Si≤1000<Si≤100
0<vij,wij≤100
import java.util.Scanner;
/**
* @author mys
* @date 2022/2/26 17:19
*
* 输入:
3 5
2
1 2
2 4
1
3 4
1
4 5
* 输出:8
*/
public class p5_dp_groupBag {
static int N = 110;
public static void main(String[] args) {
int[][] v = new int[N][N], w = new int[N][N];//v体积 w价值
int[] s = new int[N], f = new int[N];//s[i]:第i个物品的数量 f:状态
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();//物品个数
int m = sc.nextInt();//背包容量
//输入物品信息
for (int i = 1; i <= n; i ++) {
s[i] = sc.nextInt();
for (int j = 0; j < s[i]; j ++) {
v[i][j] = sc.nextInt();
w[i][j] = sc.nextInt();
}
}
//遍历
//用的上层状态,由大到小遍历体积
//用的本层状态,由小到大遍历体积
for (int i = 1; i <= n; i ++) {//物品数
for (int j = m; j >= 0; j --) {//背包容量
for (int k = 0; k < s[i]; k ++) {//k个i物品
if (v[i][k] <= j) {
f[j] = Math.max(f[j], f[j - v[i][k]] + w[i][k]);
}
}
}
}
System.out.println(f[m]);
}
}
2、线性DP
3、 区间DP