完全背包问题(Java)

完全背包问题

需要先了解 01背包问题详解

题目

有 NN 种物品和一个容量是V 的背包,每种物品都有无限件可用。

第 ii 种物品的体积是 vi,价值是 wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,NV,用空格隔开,分别表示物品种数和背包容积。

接下来有 NN 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

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背包问题**中,我们已经讨论了 输入(即数组是从第一位,还是第二位)对状态转移方程的影响,在完全背包问题中,就不再赘述;本文重点讨论如何一步一步优化代码

朴素解法

思路:

1.从前i个物品中选择,选择物品的总重量不超过j,的情况下获得的物品最大值

2.i表示物品的选择范围,所以有:i的范围是[1—n]

3.j表示枚举每一个重量上限,所以有:j的范围是[1—bagVolume]

4…k表示枚举每一个物品的选择个数,所以有:K个物品的范围是[0—k*volume[i]]

5…考虑第一个情况,dp[1,0]表示选择一个物品,背包的最大重量为0时的最大价值,考虑其意义,值为0,所以dp数组无需初始化

代码:
import java.util.Scanner;

/**
 * @see 完全背包问题 https://www.acwing.com/problem/content/3/
 */

public class completeBackpackIOClass {
    static int N = 1010;
    static int n, bagVolume;
    static int[] volume = new int[N];
    static int[] worth = new int[N];
    static int[][] dp = new int[N][N];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        bagVolume = sc.nextInt();
        //一定要注意,输入输出版的背包问题,2个数组都是从第二个数开始填入的
        for (int i = 1; i <= n; i++) {
            volume[i] = sc.nextInt();
            worth[i] = sc.nextInt();
        }

        for(int i = 1; i <= n; i++)
            for(int j = 0; j <= bagVolume; j++)
                for (int k = 0;k*volume[i]<=j;k++)
                    dp[i][j] = Math.max(dp[i][j],dp[i-1][j-k*volume[i]]+worth[i]);
        System.out.println(dp[n][bagVolume]);

    }

}

时间优化

时间优化原理:

把枚举数量的一层循环去掉,等价于:dp[i][j]= max(dp[i - 1][j], dp[i - 1][j - v] + w, dp[i - 1][j - 2 * v] + 2 * w, dp[i - 1][j - 3 * v] + 3 * w);

而列举后发现:

​ dp[i][j - v] = max(dp[i - 1][j - v], dp[i - 1][j - 2 * v] + w, dp[i - 1][j - 3 * v] + 2 * w, .....);
​ 发现:

dp[i][j - v]+ wdp[i][j]= max(dp[i - 1][j],...)中的...完全相等,因此用dp[i][j - v]+w代换...后得 :
dp[i][j] = max(dp[i - 1][j], dp[i][j - v] +w);

package ACwing3完全背包问题;

/**
 * @see 完全背包问题 https://www.acwing.com/problem/content/3/
 */
import java.util.Scanner;

public class completeBagTimeOptimizationClass {
    static int N = 1010;
    static int n, bagVolume;
    static int[] volume = new int[N];
    static int[] worth = new int[N];
    static int[][] dp = new int[N][N];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        bagVolume = sc.nextInt();
        //一定要注意,输入输出版的背包问题,2个数组都是从第二个数开始填入的
        for (int i = 1; i <= n; i++) {
            volume[i] = sc.nextInt();
            worth[i] = sc.nextInt();
        }
        /**
         * 时间优化的原理:
         * 把枚举数量的一层循环去掉,等价于:
         * dp[i][j]= max(dp[i - 1][j], dp[i - 1][j - v] + w, dp[i - 1][j - 2 * v] + 2 * w, dp[i - 1][j - 3 * v] + 3 * w);
         * 而,列举后发现:
         * dp[i][j - v] = max(dp[i - 1][j - v], dp[i - 1][j - 2 * v] + w, dp[i - 1][j - 3 * v] + 2 * w, .....);
         * 发现dp[i][j - v]+ w 与 dp[i][j]= max(dp[i - 1][j],...) 中的...完全相等,因此用dp[i][j - v]+w代换...后得 :
         * dp[i][j] = max(dp[i - 1][j], dp[i][j - v] +w);
         */
        for(int i = 1; i < n; i++) {
            for(int j = 0; j < bagVolume; j++) {
                dp[i][j] = dp[i-1][j];
                if (j>=volume[i]){
                    dp[i][j] = Math.max(dp[i][j],dp[i][j-volume[i]]+worth[i]);
                }
            }
        }
        System.out.println(dp[n][bagVolume]);
    }

}

时间,空间双优化

空间优化:

按照对01背包问题优化的思路,在时间优化版本的基础上,进行空间优化
仔细观察 完全背包问题的最终优化版,和01背包问题非常相似
唯一的不同就是内层循环的遍历顺序
实际上01背包从大到小就是使用上一行的状态更新。完全背包是使用当前行的状态更新,因为当前物品有无限个
如果01背包不倒着来,就相当于本来只能用1次的物品可以多次使用了

package ACwing3完全背包问题;

import java.util.Scanner;

public class completeBagDoubleOptimizationClass {
    static int N = 1010;
    static int n, bagVolume;
    static int[] volume = new int[N];
    static int[] worth = new int[N];
    static int[] dp = new int[N];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        bagVolume = sc.nextInt();
        //一定要注意,输入输出版的背包问题,2个数组都是从第二个数开始填入的
        for (int i = 1; i <= n; i++) {
            volume[i] = sc.nextInt();
            worth[i] = sc.nextInt();
        }
        /**
         * 空间优化:
         * 按照对01背包问题优化的思路,在时间优化版本的基础上,进行空间优化
         * 仔细观察 完全背包问题的最终优化版,和01背包问题非常相似
         * 唯一的不同就是内层循环的遍历顺序
         * 实际上01背包从大到小就是使用上一行的状态更新。完全背包是使用当前行的状态更新,因为当前物品有无限个
         * 如果01背包不倒着来,就相当于本来只能用1次的物品可以多次使用了
         */
        for (int i = 1; i < n; i++) {
            for (int j = volume[i]; j < bagVolume; j++) {
                dp[j] = Math.max(dp[j], dp[j - volume[i]] + worth[i]);
            }
        }
        System.out.println(dp[bagVolume]);
    }


}

终极优化版(在输入时同步处理)

import java.util.Scanner;
public class completeBagDoubleOptimizationPlusClass {
    static int N = 1010;
    static int[] dp = new int[N];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n  =sc.nextInt(),bagVolume = sc.nextInt();
        //一定要注意,输入输出版的背包问题,2个数组都是从第二个数开始填入的
        for(int i = 1; i <= n; i++) {
            int volume = sc.nextInt(),worth = sc.nextInt();
            for (int j = volume; j <=bagVolume ; j++)
                dp[j] = Math.max(dp[j], dp[j-volume]+worth);
        }
        System.out.println(dp[bagVolume]);
    }
}

刷题不迷路,欢迎关注博主的git题集,这里有最详细的分类,最经典的例题和最全面的注释


在这里插入图片描述

感兴趣的同学可以赏个star吗:算法刷题集git地址       题目源码地址