背包九讲
背包九讲
01背包
有 NN 件物品和一个容量是 VV 的背包。每件物品只能使用一次。
第 ii 件物品的体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000输入样例
4 5 1 2 2 4 3 4 4 5
输出样例:
8
二维数组
import java.util.*;
public class Main {
// f[i][j]
// 1.不选第i个物品 f[i][j]=f[i-1][j]
// 2.选第i个物品 f[i][j]=f[i-1][j-v[i]]
// f[0][0]=0;
// f[i][j]=max(1,2);
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int N=1010;
int f[][]=new int[N][N];//前i个物品体积为j的时候的最大价值
int v[]=new int[N];//体积
int w[]=new int[N];//价值
int n=in.nextInt();//数量
int m=in.nextInt();//体积
for(int i=1;i<=n;i++){
v[i]=in.nextInt();
w[i]=in.nextInt();
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j];//不选第i个物品
if(j>=v[i])//选第i个物品
f[i][j]=Math.max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
System.out.println(f[n][m]);
}
}
一维数组
import java.util.*;
public class Main {
static final int N=1010;
public static void main(String args[]){
Scanner in=new Scanner(System.in);
int n=in.nextInt();
int m=in.nextInt();
int v[]=new int[N];
int w[]=new int[N];
int dp[]=new int[N];
for(int i=1;i<=n;i++){
v[i]=in.nextInt();
w[i]=in.nextInt();
}
/*将二维数组压缩成一维数组不断更新
dp[j]从最大容量开始更新,否则会将上一次循环的给更新掉
j前边的部分就是二维数组前i-1个的结果
所以从后边开始更新就不会影响结果
如果使用顺序,会先更新f[4],再更新f[7],对于这个书包问题来讲,
就是有可能,在更新f[4]的时候,已经把这次能加的物品加进来了,
然后更新f[7]的时候,还有可能再加一次,所以必须使用逆序,
保证,f[4]是没有加入新物品前,背包里的最优解。
*/
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
// if(j>=v[i])可以优化到循环内部
dp[j]=Math.max(dp[j],dp[j-v[i]]+w[i]);
}
}
System.out.println(dp[m]);
}
}
初始化情况
f[0] 的初始值是0 其余都是负无穷(正好装满的时候最大值)
除0外初始化为负无穷,那么仅刚好装下的位置是正数(从0加),其余位置为负数(从负无穷加)
求恰好装满的最大价值,需要枚举一遍。
f[] 的初始值都是0(<=m时的最大值)
完全背包问题
有 NN 种物品和一个容量是 VV 的背包,每种物品都有无限件可用。
第 ii 种物品的体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000输入样例
4 5 1 2 2 4 3 4 4 5
输出样例:
10
完全背包和01背包的唯一区别就是一个物品可以用无限次
初始化跟01背包一样
import java.util.*;
public class Main{
public static void main(String args[]){
Scanner in=new Scanner(System.in);
int N=1010;
int n=in.nextInt();
int m=in.nextInt();
int dp[]=new int[N];
int v[]=new int[N];
int w[]=new int[N];
for(int i=1;i<=n;i++){
v[i]=in.nextInt();
w[i]=in.nextInt();
}
for(int i=1;i<=n;i++){
for(int j=v[i];j<=m;j++){
//从0开始因为每个物品可以重复放入
//可以省掉判断,放入循环中
dp[j]=Math.max(dp[j],dp[j-v[i]]+w[i]);
}
}
System.out.println(dp[m]);
}
}
多重背包问题 I
有 NN 种物品和一个容量是 VV 的背包。
第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000<N,V≤100
0<vi,wi,si≤1000<vi,wi,si≤100输入样例
4 5 1 2 3 2 4 1 3 4 3 4 5 2
输出样例:
10
完全背包问题由于物品数量无限,在状态转移时,当考虑放入某个物品时,是基于当前物品已经考虑过的状态进行转移,与 0 - 1 背包和多重背包基于上一个物品的状态转移有所不同。
多重背包可以看作是01背包的延申 可以放入0,1,2,3······s
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int N=200;
int n=in.nextInt();
int m=in.nextInt();
int dp[]=new int[N];
int v[]=new int[N];
int w[]=new int[N];
int s[]=new int[N];
for(int i=1;i<=n;i++) {
v[i]=in.nextInt();
w[i]=in.nextInt();
s[i]=in.nextInt();
}
for(int i=1;i<=n;i++) {
for(int j=m;j>=v[i];j--) {
for(int k=1;k<=s[i]&&k*v[i]<=j;k++)
dp[j]=Math.max(dp[j], dp[j-k*v[i]]+k*w[i]);
}
}
System.out.println(dp[m]);
}
}
多重背包问题 II
有 NN 种物品和一个容量是 VV 的背包。
第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤10000<N≤1000
0<V≤20000<V≤2000
0<vi,wi,si≤20000<vi,wi,si≤2000提示:
本题考查多重背包的二进制优化方法。
输入样例
4 5 1 2 3 2 4 1 3 4 3 4 5 2
输出样例:
10
import java.util.Scanner;
public class Main {
// 定义常量,N 用于存储分组后的物品数量,M 用于存储背包的最大容量
static final int N = 12010, M = 2010;
// n 表示物品的原始种类数,m 表示背包的容量
static int n, m;
// v 数组存储每个分组物品的体积,w 数组存储每个分组物品的价值
static int[] v = new int[N];
static int[] w = new int[N];
// f 数组用于动态规划,存储不同容量下的最大价值
static int[] f = new int[M];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取物品的原始种类数和背包的容量
n = scanner.nextInt();
m = scanner.nextInt();
// cnt 用于记录分组后的物品组数
int cnt = 0;
// 遍历每一种原始物品
for (int i = 1; i <= n; i++) {
// a 表示单个物品的体积,b 表示单个物品的价值,s 表示该物品的数量
int a = scanner.nextInt();
int b = scanner.nextInt();
int s = scanner.nextInt();
// k 用于二进制拆分,从 1 开始
int k = 1;
// 进行二进制拆分
while (k <= s) {
cnt++;
// 计算当前分组的体积
v[cnt] = a * k;
// 计算当前分组的价值
w[cnt] = b * k;
// 减去已经拆分出去的物品数量
s -= k;
// 二进制倍数增加
k *= 2;
}
// 如果还有剩余物品,单独作为一组
if (s > 0) {
cnt++;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
// 将分组后的物品组数赋值给 n
n = cnt;
// 0 - 1 背包一维优化
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]);
}
}
// 输出背包容量为 m 时的最大价值
System.out.println(f[m]);
scanner.close();
}
}
混合背包问题
有 NN 种物品和一个容量是 VV 的背包。
物品一共有三类:
- 第一类物品只能用1次(01背包);
- 第二类物品可以用无限次(完全背包);
- 第三类物品最多只能用 sisi 次(多重背包);
每种体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
- si=−1si=−1 表示第 ii 种物品只能用1次;
- si=0si=0 表示第 ii 种物品可以用无限次;
- si>0si>0 表示第 ii 种物品可以使用 sisi 次;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000
−1≤si≤1000−1≤si≤1000输入样例
4 5 1 2 -1 2 4 1 3 4 0 4 5 2
输出样例:
8
import java.util.Scanner;
public class Main {
// 定义数组的最大长度
static final int MAX_LENGTH = 100010;
// n 表示物品的种类数,m 表示背包的容量
static int n, m;
// v 数组存储物品的体积,w 数组存储物品的价值,f 数组用于动态规划
static int[] v = new int[MAX_LENGTH];
static int[] w = new int[MAX_LENGTH];
static int[] f = new int[MAX_LENGTH];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取物品的种类数和背包的容量
n = scanner.nextInt();
m = scanner.nextInt();
// cnt 用于记录二进制拆分后物品的总数
int cnt = 1;
// 遍历每种物品
for (int i = 1; i <= n; i++) {
// a 表示单个物品的体积,b 表示单个物品的价值,s 表示该物品的数量
int a = scanner.nextInt();
int b = scanner.nextInt();
int s = scanner.nextInt();
// 处理特殊情况:若 s 为负数,将其视为 1 个物品
if (s < 0) {
s = 1;
}
// 若 s 为 0,将其视为完全背包问题,计算该物品最多能取的数量
else if (s == 0) {
s = m / a;
}
// 进行二进制拆分
int k = 1;
while (k <= s) {
v[cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2;
cnt++;
}
// 处理剩余的物品
if (s > 0) {
v[cnt] = s * a;
w[cnt] = s * b;
cnt++;
}
}
// 进行 0 - 1 背包的动态规划
for (int i = 1; i < cnt; i++) {
for (int j = m; j >= v[i]; j--) {
f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
}
}
// 输出背包容量为 m 时能获得的最大价值
System.out.println(f[m]);
scanner.close();
}
}
二维费用的背包问题
有 NN 件物品和一个容量是 VV 的背包,背包能承受的最大重量是 MM。
每件物品只能用一次。体积是 vivi,重量是 mimi,价值是 wiwi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。输入格式
第一行三个整数,N,V,MN,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 NN 行,每行三个整数 vi,mi,wivi,mi,wi,用空格隔开,分别表示第 ii 件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤10000<N≤1000
0<V,M≤1000<V,M≤100
0<vi,mi≤1000<vi,mi≤100
0<wi≤10000<wi≤1000输入样例
4 5 6 1 2 3 2 4 4 3 4 5 4 5 6
输出样例:
8
import java.util.Scanner;
public class Main {
// 定义常量 N,用于数组的大小
static final int N = 1005;
// n 表示物品的数量,V 表示背包的体积容量,M 表示背包的重量容量
static int n, V, M;
// v 数组存储每个物品的体积,m 数组存储每个物品的重量,w 数组存储每个物品的价值
static int[] v = new int[N];
static int[] m = new int[N];
static int[] w = new int[N];
// f 数组用于动态规划,f[j][k] 表示体积为 j 且重量为 k 时的最大价值
static int[][] f = new int[N][N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取物品数量、背包体积容量和背包重量容量
n = scanner.nextInt();
V = scanner.nextInt();
M = scanner.nextInt();
// 读取每个物品的体积、重量和价值
for (int i = 1; i <= n; i++) {
v[i] = scanner.nextInt();
m[i] = scanner.nextInt();
w[i] = scanner.nextInt();
}
// 进行动态规划,使用 0 - 1 背包的思路
for (int i = 1; i <= n; i++) {
for (int j = V; j >= v[i]; j--) {
for (int k = M; k >= m[i]; k--) {
// 状态转移方程,更新最大价值
f[j][k] = Math.max(f[j - v[i]][k - m[i]] + w[i], f[j][k]);
}
}
}
// 输出体积为 V 且重量为 M 时的最大价值
System.out.println(f[V][M]);
scanner.close();
}
}
分组背包问题
有 NN 组物品和一个容量是 VV 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vijvij,价值是 wijwij,其中 ii 是组号,jj 是组内编号。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,VN,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 NN 组数据:
- 每组数据第一行有一个整数 SiSi,表示第 ii 个物品组的物品数量;
- 每组数据接下来有 SiSi 行,每行有两个整数 vij,wijvij,wij,用空格隔开,分别表示第 ii 个物品组的第 jj 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000<N,V≤100
0<Si≤1000<Si≤100
0<vij,wij≤1000<vij,wij≤100输入样例
3 5 2 1 2 2 4 1 3 4 1 4 5
输出样例:
8
import java.util.Scanner;
public class Main {
// 定义常量 N,作为数组的最大容量
static final int N = 110;
// f 数组用于存储状态,f[i][j] 表示只从前 i 组物品中选,当前体积小于等于 j 的最大值
static int[][] f = new int[N][N];
// v 数组存储物品体积,w 数组存储物品价值,s 数组存储每组物品的个数
static int[][] v = new int[N][N];
static int[][] w = new int[N][N];
static int[] s = new int[N];
// n 表示物品的组数,m 表示背包的容量
static int n, m;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取物品的组数和背包的容量
n = scanner.nextInt();
m = scanner.nextInt();
// 读取每组物品的信息
for (int i = 1; i <= n; i++) {
s[i] = scanner.nextInt();
for (int j = 0; j < s[i]; j++) {
v[i][j] = scanner.nextInt();
w[i][j] = scanner.nextInt();
}
}
// 动态规划过程
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
// 不选当前组物品的情况
f[i][j] = f[i - 1][j];
// 枚举当前组的每个物品
for (int k = 0; k < s[i]; k++) {
if (j >= v[i][k]) {
// 更新最大价值
f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
}
}
}
}
// 输出结果
System.out.println(f[n][m]);
scanner.close();
}
}
import java.util.Scanner;
public class Main {
// 定义常量 N,作为数组的最大容量
static final int N = 110;
// f 数组用于存储状态,f[j] 表示当前体积小于等于 j 的最大值
static int[] f = new int[N];
// v 数组存储物品体积,w 数组存储物品价值,s 数组存储每组物品的个数
static int[][] v = new int[N][N];
static int[][] w = new int[N][N];
static int[] s = new int[N];
// n 表示物品的组数,m 表示背包的容量
static int n, m;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取物品的组数和背包的容量
n = scanner.nextInt();
m = scanner.nextInt();
// 读取每组物品的信息
for (int i = 0; i < n; i++) {
s[i] = scanner.nextInt();
for (int j = 0; j < s[i]; j++) {
v[i][j] = scanner.nextInt();
w[i][j] = scanner.nextInt();
}
}
// 动态规划过程
for (int i = 0; i < n; i++) {
for (int j = m; j >= 0; j--) {
// 枚举当前组的每个物品
for (int k = 0; k < s[i]; k++) {
if (j >= v[i][k]) {
// 更新最大价值
f[j] = Math.max(f[j], f[j - v[i][k]] + w[i][k]);
}
}
}
}
// 输出结果
System.out.println(f[m]);
scanner.close();
}
}