背包问题是比较经典的dp问题,目前背包九讲做了5讲,均为基础题,所以下面进行每道的具体总结和分析。
01背包问题
这道01背包问题就是选与不选,关键在于把二维数组代码等价替换与一维数组,简化了计算量。是从大到小进行枚举,主要是因为从上一层没有更新的,替换过来
import java.util.Scanner;
/**
* Description:01背包问题
*/
public class package01 {
static int N = 1010;
static int[] f= new int[N];
static int[] w= new int[N];
static int[] v= new int[N];
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int V = 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 = V; j >=v[i] ; j--) { //应该是j要大于=当前物体的体积,才能加进去
f[j] = Math.max(f[j],f[j-v[i]]+w[i]);//他需要更新的话,需要使用没有用过的量,如果从小到大的话,可能会存在你已经减去了这个物品,然后再减一次的情况,但是从大到小就不会出现这种情况
}
}
// int[][] f = new int[N + 1][V + 1]; 这是利用二维数组去完成的
// for(int i = 1;i <= N;i ++) {
// for(int j = 1 ;j <= V ;j ++) {
// if (v[i] > j) {
// f[i][j] = f[i - 1][j];
// } else {
// f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
// }
// }
// }
System.out.println(f[V]);
}
}
完全背包问题
然后是完全背包问题,跟01背包的区别在于它是每件物品可以选无数次,所以这里的状态更新是从小到大,从v[i]开始
第一种做法:
import java.util.Arrays;
import java.util.Scanner;
/**
* Description:3. 完全背包问题
*/
public class package03 {
static int N = 1010;
static int[] f = new int[N];
static int[] w = new int[N];
static int[] v = new int[N];
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int N = in.nextInt();
int V = 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 = v[i]; j <=V ; j++) { // 这里是跟01背包不同地方,也是关键
f[j] = Math.max(f[j],f[j-v[i]]+w[i]);
}
}
System.out.println(f[V]);
}
}
第二种做法:
import java.util.Scanner;
public class Main {
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 + 1];
int[] w = new int[n + 1];
for(int i = 1;i <= n;i ++) {
v[i] = in.nextInt();
w[i] = in.nextInt();
}
int[] f = new int[m + 1];
for(int i = 1;i <= n;i ++) {
for(int j = m;j >= 0 ;j--) {
for(int k = 1;k*v[i] <= j;k++) { // 第二种做法在这用了第三重循环
f[j] = Math.max(f[j], f[j - k *v[i]] + k * w[i]);
}
}
}
System.out.println(f[m]);
}
}
多重背包问题
紧接着就是多重背包问题,这个的区别就是可选的物品给了参数,多少件,然后选取最大值。这里就多了一重循环,也就是现在是三重循环,二维数组,从小到大枚举,为什么是二维数组,主要是因为不能代码等价替换了。
import java.util.Scanner;
/**
* Description:4. 多重背包问题 I
*/
public class package04 {
static int N = 1010;
static int[] v = new int[N];
static int[] w = new int[N];
static int[] s = new int[N];
static int[][] f = new int[N][N];
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
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 = 0; j <=m ; j++) {
for (int k = 0; k <=s[i]; k++) {
if (j-k*v[i]>=0)
f[i][j] = Math.max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
System.out.println(f[n][m]);
}
}
接着就是必须要用二进制方法进行优化的01背包问题,要不然一定会超时的,这个的解法适合如果想钻研算法的同学进行观看,如果是目前时间很近或者要准备面试笔试的同学可以暂时不看
import java.util.Scanner;
/**
* Description: 这道题主要是时间限制,所以要有二进制方法进行优化
*/
public class package05 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int N = 12000;
int[] f = new int[N];
int[] v = new int[N];
int[] w = new int[N];
int cnt = 0; //计算组合个数
for (int i = 0; i < n; i++) { //这块就是在进行二进制优化,其实就是把10分成由2的倍数组成的数字,如果分不完全就由if那个判断进行消化。
int vv = in.nextInt();
int ww = in.nextInt();
int s = in.nextInt();
int k =1;
while (s>=k) {
cnt++;
v[cnt] = k*vv;
w[cnt] = k*ww;
s-=k;
k*=2;
}
if (s>0) {
cnt++;
v[cnt] = s*vv;
w[cnt] = s*ww;
}
}
n = cnt;
for (int i = 0; i <= cnt; 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]);
}
}
接着就是分组背包问题,这个的话跟01背包很像,区别在于是从每组选取一个物品进行更新。其实就是把v,w这种一维数组变为二维数组进行状态更新。
import java.util.Scanner;
public class package09 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int N = 1010;
int[][] v = new int[N][N];
int[][] w = new int[N][N];
int[] f = new int[N];
int[] s = new int[N];
int n = in.nextInt();
int m = in.nextInt();
for (int i = 1; i <= n ; i++) {
s[i] = in.nextInt();
for (int j = 1; j <= s[i]; j++) {
int a = in.nextInt();
int b = in.nextInt();
v[i][j] = a;
w[i][j] = b;
}
}
for (int i = 1; i <=n ; i++) {
for (int j = m; j >0 ; j--) {
for (int k = 0; k <= s[i]; k++) { // 从0开始,因为可以不选
if (v[i][k] <=j) {
f[j] = Math.max(f[j],f[j-v[i][k]]+w[i][k]);
}
}
}
}
/**
* 下面这个循环时第二种方法,一定要有temp要不然就会陷入只存储最后一次更新的变量,而不是最大变量
*
**/
for(int i = 1; i <= n; i++) {
for(int j = m;j >= 0; j--) {
int temp = f[i - 1][j]; //在内层的 for 循环中引入一个临时变量来存储可能的最大值,然后在循环结束后再更新 f[i][j]。
for(int k = 0; k <= s[i];k ++) {
if ((j -v[i][k]) >= 0) {
temp = Math.max(temp, f[i - 1][j -v[i][k]] + w[i][k]);
}
}
f[i][j] = temp;
}
}
System.out.println(f[m]);
}
}