zoukankan      html  css  js  c++  java
  • 背包问题专栏

    一、01背包问题

    Q:

    有 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

     A:

    package acwing.packageQ;
    
    import java.util.Scanner;
    
    /**
     * @author Millet
     * @date 2020/5/26 9:45
     */
    public class PackageQ1 {
        public static void main(String[] args) {
            Scanner in = new Scanner(System.in);
            int n = in.nextInt();
            int v = in.nextInt();
            int[] weight = new int[n+1];
            int[] value = new int[n+1];
            for(int i=1;i<=n;i++){
                weight[i] = in.nextInt();
                value[i] = in.nextInt();
            }
            int res = maxValue(value, weight,n,v);
            int res2 = optimize(value,weight,n,v);
            System.out.println(res);
            System.out.println(res2);//优化
        }
    
        /**
         * 动态规划:
         * 定义一个二维矩阵dp[i][j]表示前i个物品,背包容量j情况下的最优解
         * 在判断第i个物品时有两种情况:
         *      1.第i件物品加进来比背包容量大,则不选  dp[i][j] = dp[i-1][j]
         *      2.第i件物品加进来比背包容量小,则通过判断价值是否选
         *          选:前i-1件物品放入容量为j-w[i]的背包中最大价值
         *          不选:前i-1件物品放入容量为j的背包中最大价值
         *          dp[i][j] = MAX( dp[i-1][j], dp[i-][j-weight[i]]+value[i] )
         * @param value
         * @param weight
         * @param n
         * @param v
         * @return
         */
        private static int maxValue(int[] value,int[] weight, int n, int v) {
            int[][] dp = new int[n+1][v+1];
            for(int i=1;i<=n;i++){//dp[0][0-v]都为0
                for(int j=0;j<=v;j++){//背包容量
                    if(j < weight[i]){//当前物品重量大于背包容量
                        dp[i][j] = dp[i-1][j];
                    }else {
                        dp[i][j] = Math.max( dp[i-1][j], dp[i-1][j-weight[i]]+value[i] );
                    }
                }
            }
            return dp[n][v];//前n个物品,背包容量为v情况下最大价值
        }
    
        /**
         * 动态规划:一维数组
         * dp[j] = Max( dp[j], dp[j-weight[i]] + value[i] )
         * @param value
         * @param weight
         * @param n
         * @param v
         * @return
         */
        private static int optimize(int[] value, int[] weight, int n, int v) {
            int[] dp = new int[v+1];//表示当前背包重量为j的最大价值
            for(int i=1;i<=n;i++){
                //注意:这里是倒序进行遍历,是为了解决覆盖旧值问题,可以和完全背包问题进行比较
                /*
                动态规划是通过上一轮状态推导出当前一轮的状态((保证物品只使用一次或零次)),如果正序推导dp数组
                ,那么是从dp[0-v],后面的数组更新则使用了新的一轮dp值,例如dp[8]使用了
                dp[3],而dp[3]如果按照正序遍历的话,已经是新的一轮值了。
                 */
                /*
                首先dp数组初始化全为0:给定物品种类有4种,包最大体积为5,数据来源于题目的输入
                weight    value
                  1         2
                  2         4
                  3         4
                  4         5
                dp[j] = Math.max( dp[j], dp[j-weight[i]] + value[i])
    
                [0,0,0,0,0,0]
                i = 1时:j从5到weight[1]
                dp[5] = max(dp[5],dp[4]+value[1]) = value[1] = 2
                dp[4] = max(dp[4],dp[3]+value[1]) = value[1] = 2
                dp[3] = max(dp[3],dp[2]+value[1]) = value[1] = 2
                dp[2] = max(dp[2],dp[1]+value[1]) = value[1] = 2
                dp[1] = max(dp[1],dp[0]+value[1]) = value[1] = 2
    
                [0,2,2,2,2,2]
                i = 2时:j从5到weight[2]
                dp[5] = max(dp[5],dp[3]+value[2]) = dp[3]+value[2] = 6
                dp[4] = max(dp[4],dp[2]+value[2]) = dp[2]+value[2] = 6
                dp[3] = max(dp[3],dp[1]+value[2]) = dp[1]+value[2] = 6
                dp[2] = max(dp[2],dp[0]+value[2]) = dp[0]+value[2] = 4
    
                [0,2,4,6,6,6]
                i = 3 时:j从5到weight[3]
                dp[5] = max(dp[5],dp[2]+value[3]) = dp[2]+value[3] = 8
                dp[4] = max(dp[4],dp[1]+value[3]) = dp[1]+value[3] = 6
                dp[3] = max(dp[3],dp[0]+value[3]) = dp[3] = 6
    
                [0,2,4,6,6,8]
                i = 4 时: j从5到weight[4]
                dp[5] = max(dp[5],dp[1]+value[4]) = dp[5] = 8
                dp[4] = max(dp[4],dp[0]+value[4]) = dp[4] = 6
    
                [0,2,4,6,6,8]
                 */
                for(int j=v;j>=weight[i];j--){
                    dp[j] = Math.max( dp[j], dp[j-weight[i]] + value[i]);
                }
            }
            return dp[v];
        }
    }

      二、完全背包问题

    Q:

       题目如上题,只不过条件变成“ 每种物品都有无限件可用

    A:

    package acwing.packageQ;
    
    import java.util.Scanner;
    
    /**
     * @author Millet
     * @date 2020/5/26 11:01
     */
    public class PackageQ2 {
        public static void main(String[] args) {
            Scanner in = new Scanner(System.in);
            int n = in.nextInt();
            int v = in.nextInt();
            int[] weight = new int[n + 1];
            int[] value = new int[n + 1];
            for (int i = 1; i <= n; i++) {
                weight[i] = in.nextInt();
                value[i] = in.nextInt();
            }
            System.out.println(optimize(value,weight,n,v));
        }
    /**
         * 动态规划:一维数组
         * @param value
         * @param weight
         * @param n
         * @param v
         * @return
         */
        private static int optimize(int[] value, int[] weight, int n, int v) {
            int[] dp = new int[v+1];
            for(int i=1;i<=n;i++){
                //注意:此处正序遍历,和01背包问题不同,本题物品可重复使用,因此不在乎是否使用新值
                /*
                首先dp数组初始化全为0:给定物品种类有4种,包最大体积为5,数据来源于题目的输入
                weight    value
                  1         2
                  2         4
                  3         4
                  4         5
    
                i = 1 时: j从weight[1]到5
                dp[1] = max(dp[1],dp[0]+value[1]) = value[1] = 2 (用了一件物品1)
                dp[2] = max(dp[2],dp[1]+value[1]) = value[1] + value[1] = 4(用了两件物品1)
                dp[3] = max(dp[3],dp[2]+value[1]) = value[1] + value[1] + value[1] = 6(用了三件物品1)
                dp[4] = max(dp[4],dp[3]+value[1]) = value[1] + value[1] + value[1] + value[1] = 8(用了四件物品1)
                dp[5] = max(dp[3],dp[2]+value[1]) = value[1] + value[1] + value[1] + value[1] + value[1] = 10(用了五件物品)
    
                i = 2 时:j从weight[2]到5
                dp[2] = max(dp[2],dp[0]+value[2]) = value[1] + value[1] = value[2] =  4(用了两件物品1或者一件物品2)
                dp[3] = max(dp[3],dp[1]+value[2]) = 3 * value[1] = value[1] + value[2] =  6(用了三件物品1,或者一件物品1和一件物品2)
                dp[4] = max(dp[4],dp[2]+value[2]) = 4 * value[1] = dp[2] + value[2] =  8(用了四件物品1或者,两件物品1和一件物品2或两件物品2)
                dp[5] = max(dp[5],dp[3]+value[2]) = 5 * value[1] = dp[3] + value[2] =  10(用了五件物品1或者,三件物品1和一件物品2或一件物品1和两件物品2)
    
                i = 3时:j从weight[3]到5
                dp[3] = max(dp[3],dp[0]+value[3]) = dp[3] = 6 # 保持第二轮的状态
                dp[4] = max(dp[4],dp[1]+value[3]) = dp[4] = 8 # 保持第二轮的状态
                dp[5] = max(dp[5],dp[2]+value[3]) = dp[4] = 10 # 保持第二轮的状态
    
                i = 4时:j从weight[4]到5
                dp[4] = max(dp[4],dp[0]+value[4]) = dp[4] = 10 # 保持第三轮的状态
                dp[5] = max(dp[5],dp[1]+value[4]) = dp[5] = 10 # 保持第三轮的状态
                 */
                for(int j=weight[i];j<=v;j++){
                    dp[j] = Math.max( dp[j], dp[j-weight[i]] + value[i]);
                }
            }
            return dp[v];
        }
    }

      

    三、多重背包问题Ⅰ

    Q:

    有 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 

    A1:

      可以理解成01背包问题的变种,将多个相同的物品看作个体。

    package acwing.packageQ;
    
    import java.util.Scanner;
    
    /**
     * @author Millet
     * @date 2020/5/26 14:57
     */
    public class PackageQ3 {
        public static void main(String[] args) {
            Scanner in = new Scanner(System.in);
            int n = in.nextInt();
            int v = in.nextInt();
            int[] weight = new int[n + 1];
            int[] value = new int[n + 1];
            int[] num = new int[n + 1];
            for (int i = 1; i <= n; i++) {
                weight[i] = in.nextInt();
                value[i] = in.nextInt();
                num[i] = in.nextInt();
            }
            System.out.println(maxValue(value,weight,num,n,v));
        }
    
        private static int maxValue(int[] value, int[] weight, int[] num, int n, int v) {
            int[] dp = new int[v+1];
            for(int i=1;i<=n;i++){//dp[0][0-v]都为0
                for(int j=v;j>=weight[i];j--){
                    for(int k=1;k<=num[i];k++){
                        if(j >= k*weight[i])
                            dp[j] = Math.max( dp[j], dp[j-k*weight[i]] + k*value[i]);
                    }
                }
            }
            return dp[v];
        }
    }

    A2:二进制优化:

    package acwing.packageQ;
    
    import java.util.Scanner;
    
    /**
     * @author Millet
     * @date 2020/5/26 19:43
     */
    public class PackageQ4 {
        public static void main(String[] args) {
            Scanner in = new Scanner(System.in);
            int n = in.nextInt();
            int v = in.nextInt();
            /* 使用二进制拆分
             *      1. 普通方法是将相同的多个物品拆看成单个特例,进行逐个遍历比较,
             *      但是每次都要遍历num[i]次,复杂。例如11个相同物品,需要遍历11次。
             *      2.使用二进制拆分成几个数来表示1~num[i],例如1 2 4 4,可以表示1~11
             *      Q:那么是如何获得这几个数呢?
             *          原数不断减去2的整次幂,直到最后比剩余数大,无法再减,则再加上最后一个
             *          11-1=10,10-2=8,8-4=4,4>=4直接添加
             */
            //构造新的value,weight
            //0<V<2000,数组最大容量每个si能拆出来的个数 + 左右预留的哨兵位置 = 2000 * log2(2000) + 2 <= 22002
            int maxN = 22002;
            int[] weight = new int[maxN];
            int[] value = new int[maxN];
            int p=1;//数组索引
            for (int i = 1; i <= n; i++) {
                int W = in.nextInt();
                int V = in.nextInt();
                int S = in.nextInt();
                for(int k=1;k<S;k*=2){
                    weight[p] = k*W;
                    value[p] = k*V;
                    S-=k;
                    p++;
                }
                if(S>0){//剩余
                    weight[p] = S*W;
                    value[p] = S*V;
                    p++;
                }
            }
            System.out.println(maxValueBinary(value,weight,n,v));
        }
        /**
         *
         * @param value
         * @param weight
         * @param n
         * @param v
         * @return
         */
        private static int maxValueBinary(int[] value, int[] weight, int n, int v) {
            //构建好数组就变成了01背包问题
            int[] dp = new int[v+1];
            for(int i=1;i<=n;i++){
                for(int j=v;j>=weight[i];j--){
                    dp[j] = Math.max( dp[j], dp[j-weight[i]] + value[i]);
                }
            }
            return dp[v];
        }
    }
  • 相关阅读:
    oracle锁---原理篇
    SML + NL + HJ
    Oracle中varchar,varchar2,nvarchar,nvarchar2的区别
    oracle 一致读原理
    commit 流程
    IMPDP NETWORK_LINK参数
    WINDOWS访问虚拟机RedHat搭配的Apache2服务器
    初识malloc函数
    好吧,又失眠
    休息一天
  • 原文地址:https://www.cnblogs.com/qmillet/p/12964230.html
Copyright © 2011-2022 走看看