1. 01背包问题
有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。
第 i件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
思路
(1)状态f[i][j]定义:前 i 个物品,背包容量 j 下的最优解(最大价值):
当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,有 N 件物品,则需要 N 次决 策,每一次对第 i 件物品的决策,状态f[i][j]不断由之前的状态更新而来。
(2)当前背包容量不够(j < v[i]),没得选,因此前 i 个物品最优解即为前 i−1 个物品最优解:
对应代码:f[i][j] = f[i - 1][j]。
(3)当前背包容量够,可以选,因此需要决策选与不选第 i 个物品:
选:f[i][j] = f[i - 1][j - v[i]] + w[i]。
不选:f[i][j] = f[i - 1][j] 。
我们的决策是如何取到最大价值,因此以上两种情况取 max() 。
代码实现
import java.util.*;
public class Main{
/*
* dp[i][j] 表示为前i个物品j重量的最大价值
*当v[i]>j 剩下容量放不进去 直接继承 前i-1件的最大价dp[i][j]=dp[i-1][j]
* 当v[i]<=j 有两种选择 当前物品放进去和不放进去
*放进去dp[i][j]=dp[i-1][j-v[i]]+w[i]; 不放进去 dp[i][j]=dp[i-1][j]
*/
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 + 1];
int[] w = new int[N + 1];
for (int i = 1; i <= N; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
/*
* dp[i][j]在两种情况中选择比较大的情况作为当前的最优解; if(j >= v[i]): dp[i][j] = max(dp[i-1][j],
* dp[i-1][j-v[i]] + w[i]) else: dp[i][j] = dp[i-1][j]
*/
int[][] dp = new int[N + 1][V + 1];
dp[0][0] = 0;
for (int i = 1; i <= N; i++) {
for (int j = 0; j <= V; j++) {
// 当前背包容量能装进第i个物品,则价值选和不选去Max值
if (j >= v[i]) {
dp[i][j] = Math.max(dp[i - 1][j - v[i]] + w[i], dp[i - 1][j]);
} else {
// 当前背包容量装不进第i个物品,则价值等于前i-1个物品
dp[i][j] = dp[i - 1][j];
}
}
}
System.out.println(dp[N][V]);
}
}
import java.util.Scanner;
public class Main {
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 + 1];
int[] w = new int[N + 1];
for (int i = 1; i <= N; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
int[] dp = new int[V + 1];
dp[0] = 0;
for (int i = 1; i <= N; i++) {
for (int j = V; j >= v[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
}//若j从小到大,f[j-v[i]]中,由于j-v[i]小于j,f[j-v[i]]已经在i这层循环被计算了,而我们想要的f[j-v[i]]应该是i-1层循环里面的,所以j从大到小的话保证此时的f[j-v[i]]还未被计算,也就是第i-1层的数据
}
System.out.println(dp[V]);
}
}
2. 完全背包问题
有 N 件物品和一个容量是 V的背包。每种物品都有无限件可用。
第 i件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
思路
代码实现
import java.util.Scanner;
public class Main {
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 + 1];
int[] w = new int[N + 1];
for (int i = 1; i <= N; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
int[][] dp = new int[N + 1][V + 1];
for (int i = 1; i <= N; i++) {
for (int j = 0; j <= V; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= v[i]) {
// 计算了从0~V 的所有体积转移的可能结果,所以不用再每次枚举选第i个物品几个
// 直接对应了每个物品可选无限次
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
}
}
}
System.out.println(dp[N][V]);
}
}
import java.util.Scanner;
public class Main {
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 + 1];
int[] w = new int[N + 1];
for (int i = 1; i <= N; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
int[] dp = new int[V + 1];
for (int i = 1; i <= N; i++) {
// 逆序是为了保证更新当前状态时,用到的状态是上一轮的状态,保证每个物品只有一次或零次;
// 在这里,因为每个物品可以取任意多次,所以不再强求用上一轮的状态,即本轮放过的物品,在后面还可以再放;
for (int j = v[i]; j <= V; j++) // 注意了,这里的j是从小到大枚举,和01背包不一样
dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
}
System.out.println(dp[V]);
}
}
3. 多重背包问题I
有 N 种物品和一个容量是 V 的背包。
第 i种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
思路
代码实现
import java.util.Scanner;
public class Main{
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+1];
int[]w=new int[N+1];
int[]s=new int[N+1];
int dp[]=new int[V+1];
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=V;j>=1;j--){
for(int k=1;k<=s[i]&&k<=j/v[i];k++){
dp[j]=Math.max(dp[j],dp[j-k*v[i]]+k*w[i]);
}
}
}
System.out.print(dp[V]);
}
}
4. 多重背包问题 II
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
提示:
本题考查多重背包的二进制优化方法。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
思路
这道题是二进制差分的方法来优化的多重背包问题。
这里讲一下,为什么可以那样拆分:
比如说s=127,分成几个数,按照分的思路分出来就是1,2,4,8,16,32,64大家可以试一下,这里的几个数,可以凑出所有0≤k≤127的所有数,而背包问题会选择最优解。
最后按0/10背包问题的思路进行枚举即可。
代码实现
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner([System.in](http://System.in));
int n = sc.nextInt();
int m = sc.nextInt();
int[] f = new int[m + 1];
for (int i = 0; i < n; i++) {
int v = sc.nextInt();
int w = sc.nextInt();
int s = sc.nextInt();
// 0-1背包是特殊的多重背包问题
// 二进制优化
// k是每种物品的个数
for (int k = 1; k <= s; k *= 2) {
// 使用01背包模板处理
for (int j = m; j >= k * v; j--) {
f[j] = Math.max(f[j], f[j - k * v] + k * w);
}
// 减去上一个物品的个数
s -= k;
}
// 剩下的个数不是2的幂
if (s > 0) {
for (int j = m; j >= s * v; j--) {
f[j] = Math.max(f[j], f[j - s * v] + s * w);
}
}
}
System.out.println(f[m]);
}
}
5. 混合背包问题
有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
- 第一类物品只能用1次(01背包);
- 第二类物品可以用无限次(完全背包);
- 第三类物品最多只能用 si次(多重背包);
每种体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V用空格隔开,分别表示物品种数和背包容积。
接下来有 N行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i种物品的体积、价值和数量。
- si=−1 表示第 i种物品只能用1次;
- si=0 表示第 i 种物品可以用无限次;
- si>0 表示第 i种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤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 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt(), V = sc.nextInt();
int[] v = new int[N + 1];
int[] w = new int[N + 1];
int[] s = new int[N + 1];
for(int i = 1; i <= N; i++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
s[i] = sc.nextInt();
}
int[] dp = new int[V + 1];
for(int i = 1; i <= N; i++) {
if(s[i] == -1 || s[i] > 0) {
int n = s[i] == -1 ? 1 : s[i];
for(int k = 1; k <= n; k *= 2){
for(int j = V; j >= 1; j--) {
if(j - v[i] * k >= 0) dp[j] = Math.max(dp[j], dp[j - v[i]*k] + w[i]*k);
}
n -= k;
}
if(n != 0) {
for(int j = V; j >= 1; j--) {
if(j - v[i] * n >= 0) dp[j] = Math.max(dp[j], dp[j - v[i]*n] + w[i]*n);
}
}
}
else {
for(int j = 1; j <= V; j++) if(j - v[i] >= 0) dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
}
}
System.out.println(dp[V]);
}
}
6. 二维费用的背包问题
有 N件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行三个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤10000
0<V,M≤1000
0<vi,mi≤1000
0<wi≤10000
输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
输出样例:
8
思路
代码实现
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int [][] bags=new int[1010][1010];
Scanner scanner=new Scanner(System.in);
int num=scanner.nextInt();
int volume=scanner.nextInt();
int weight=scanner.nextInt();
int v[]=new int[1010],w[]=new int[1010],val[]=new int[1010];
for (int i=0;i<num;i++){
v[i]=scanner.nextInt();
w[i]=scanner.nextInt();
val[i]=scanner.nextInt();
}
for (int i=0;i<num;i++){
for (int j=volume;j>=v[i];j--){
for (int k=weight;k>=w[i];k--){
bags[j][k]=Math.max(bags[j][k],bags[j-v[i]][k-w[i]]+val[i]);
}
}
}
System.out.println(bags[volume][weight]);
}
}
7. 分组背包问题
有 N组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i是组号,j是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N组数据:
- 每组数据第一行有一个整数 Si,表示第 i个物品组的物品数量;
- 每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i个物品组的第 j个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8
思路
代码实现
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int g = sc.nextInt();
int w = sc.nextInt();
int dp[] = new int[w+1];
for(int i = 0;i < g;i++){
int m = sc.nextInt();
int v[] = new int[m];
int w[] = new int[m];
for(int s = 0;s < m;s++){
v[s] = sc.nextInt();
w[s] = sc.nextInt();
}
for(int j = w;j >= 0;j--){
for(int k = 0;k < m;k++)
if(j >= v[k])
dp[j] = Math.max(dp[j],dp[j-v[k]]+w[k]);
}
}
System.out.println(dp[w]);
}
}
8. 背包问题求方案数
有 N件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 10^9+7 的结果。
输入格式
第一行两个整数,N,V用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示 方案数 模 10^9+7 的结果。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
2
思路![在这里插入图片描述](https://img-blog.csdnimg.cn/a5ac33bcb62642039d59fccc0750f6d4.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/4d77e3c388f14600b4b96b9edce3cab5.png)
代码实现
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int mod = 1000000007;
Scanner sc = new Scanner(System.in)
int N = sc.nextInt(), V = sc.nextInt(), mmax = 0;
int[] f = new int[V + 1];
int[] g = new int[V + 1];
for(int i = 1; i <= V; i++) f[i] = -10000;
g[0] = 1;
for(int i = 1; i <= N; i++) {
int v = sc.nextInt(), w = sc.nextInt();
for(int j = V; j >= v; j--) {
int left = f[j], right = f[j - v] + w;
f[j] = Math.max(left, right);
if(left < right) g[j] = g[j - v];
else if(left == right) g[j] = g[j] + g[j - v];
if(g[j] >= mod) g[j] -= mod;
}
}
for(int n : f) mmax = Math.max(mmax, n);
int res = 0;
for(int i = 0; i <= V; i++) {
if(f[i] == mmax) res+=g[i];
}
System.out.println(res);
}
}
9. 背包问题求具体方案
有 N件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N1…N。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。
物品编号范围是 1…N。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
1 4
思路
代码实现
import java.util.*;
public class Main{
static int N = 1010;
static int[][] f = new int[N][N];
static int[] v = new int[N];
static int[] w = new int[N];
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int V = scan.nextInt();
for(int i = 1 ; i <= n ; i++){
v[i] = scan.nextInt();
w[i] = scan.nextInt();
}
for(int i = n ; i >= 1 ;i-- ){
for(int j = 0 ; j <= V ; 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]);
}
}
int j = V ;
//f[1][V] 是最大值
for(int i = 1 ; i <= n ; i++ ){
if(j >= v[i] && f[i][j] == f[i+1][j-v[i]]+w[i] ){
System.out.print(i + " ");
j -= v[i] ;
}
}
}
}