首页 > 其他分享 >动态规划之 完全背包

动态规划之 完全背包

时间:2023-07-08 15:44:20浏览次数:48  
标签:背包 int 数组 物品 动态 规划 public dp

1. 题目

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。

 

2.  完全背包 动态规划表 


 

完全背包问题是一种动态规划问题,它的特点是每种物品可以取无限次,而不是只能取一次。

一个例子是:有一个背包的容积为5,有4种物品,每种物品的体积和价值分别为(1, 2), (2, 4), (3, 4), (4, 5)。求在不超过背包容积的情况下,能装入的最大价值是多少?

物品的信息如下:

物品编号体积价值
1 1 2
2 2 4
3 3 4
4 4 5

一个可能的动态规划表如下:

i\j012345
0 0 0 0 0 0 0
1 0 2 4 6 8 10
2 0 2 4 6 8 10
3 0 2 4 6 8 10
4 0 2 4 6 8 10

其中,i表示前i种物品,j表示背包的剩余容量,表格中的值表示最大价值。加粗的部分表示状态转移方程的更新。

状态转移方程可以表示为:

dp[i][j]=max(dp[i−1][j],dp[i][j−v[i]]+w[i])

其中,dp[i][j]表示前i种物品装入剩余容量为j的背包的最大价值,v[i]和w[i]分别表示第i种物品的体积和价值。

从表格中可以看出,最终的答案是10,即装入两个体积为1,价值为2的物品和一个体积为3,价值为4的物品。

 

3. 朴素二维写法

二维写法的三种分别是:

    • 一种是在状态转移方程中,用一个循环来枚举每种物品的取法,即k=0,1,2,…,j/v[i],然后取最大值。这种写法的时间复杂度是O(NMV),其中N是物品数量,M是背包容量,V是物品体积的最大值。这种写法的优点是比较通用和容易理解,缺点是比较慢。

      • f[i][j]=0≤k≤v[i]j  max​(f[i−1][j−kv[i]]+kw[i])
    • 另一种是在状态转移方程中,利用f[i][j-v[i]]+w[i]和f[i-1][j]的关系,将循环优化为一个max操作。这种写法的时间复杂度是O(NM),其中N是物品数量,M是背包容量。这种写法的优点是比较快,缺点是比较难想到和证明。

      • f[i][j]=max(f[i−1][j],f[i][j−v[i]]+w[i])
    • 还有一种是在状态转移方程中,将f[i][j]和f[i-1][j]的关系用一个max操作表示,然后将j的遍历顺序从逆序改为正序。这种写法的时间复杂度也是O(NM),其中N是物品数量,M是背包容量。这种写法的优点是比较简洁和巧妙,缺点是比较难理解和记忆。

      • f[i][j]=max(f[i−1][j],f[i−1][j−v[i]]+w[i])

 

3.1  二维带枚举实现

public class BackpackComplete1 {
    public static int maxValue(int n, int V, int[] v, int[] w) {
        // 动态规划数组
        int[][] dp = new int[n + 1][V + 1];
        // 初始化第一行和第一列为0
        for (int i = 0; i <= n; i++) {
            dp[i][0] = 0;
        }
        for (int j = 0; j <= V; j++) {
            dp[0][j] = 0;
        }
        // 状态转移
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= V; j++) {
                dp[i][j] = dp[i - 1][j]; // 不装第i种物品
                for (int k = 0; k * v[i - 1] <= j; k++) { // 枚举装k个第i种物品
                    dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * v[i - 1]] + k * w[i - 1]); // 比较不装和装第i种物品的最大价值
                }
            }
        }
        // 返回最大价值
        return dp[n][V];
    }
    
    public static void main(String[] args) {
        // 物品数量
        int n = 4;
        // 背包容量
        int V = 5;
        // 物品体积数组
        int[] v = {1, 2, 3, 4};
        // 物品价值数组
        int[] w = {2, 4, 4, 5};
        
        // 调用方法并输出结果
        System.out.println(maxValue(n, V, v, w));
    }
}

3.2 二维带

public class BackpackComplete2 {
    public static int maxValue(int n, int V, int[] v, int[] w) {
         // 动态规划数组
         int[][] dp = new int[n + 1][V + 1];
         // 初始化第一行和第一列为0
         for (int i = 0; i <= n; i++) {
             dp[i][0] = 0;
         }
         for (int j = 0; j <= V; j++) {
             dp[0][j] = 0;
         }
         // 状态转移
         for (int i = 1; i <= n; i++) {
             for (int j = v[i - 1]; j <= V; j++) { // 只考虑装得下第i种物品的情况
                 dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - v[i - 1]] + w[i - 1]); // 比较不装和装第i种物品的最大价值
             }
             for (int j = v[i - 1] - 1; j >= 0; j--) { // 只考虑装不下第i种物品的情况
                 dp[i][j] = dp[i - 1][j]; // 不装第i种物品
             }
         }
         // 返回最大价值
         return dp[n][V];
    }
    
    public static void main(String[] args) {
         // 物品数量
         int n = 4;
         // 背包容量
         int V = 5;
         // 物品体积数组
         int[] v = {1, 2, 3, 4};
         // 物品价值数组
         int[] w = {2, 4, 4, 5};
         
         // 调用方法并输出结果
         System.out.println(maxValue(n, V, v, w));
    }
}

3.3 二维带

public class BackpackComplete3 {
    public static int maxValue(int n, int V, int[] v, int[] w) {
         // 动态规划数组
         int[][] dp = new int[n + 1][V + 1];
         // 初始化第一行和第一列为0
         for (int i = 0; i <= n; i++) {
             dp[i][0] = 0;
         }
         for (int j = 0; j <= V; j++) {
             dp[0][j] = 0;
         }
         // 状态转移
         for (int i = 1; i <= n; i++) {
             for (int j = 1; j <= V; j++) { // 正序遍历容量
                 dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - v[i - 1]] + w[i - 1]); // 比较不装和装第i种物品的最大价值
             }
         }
         // 返回最大价值
         return dp[n][V];
    }
    
    public static void main(String[] args) {
         // 物品数量
         int n = 4;
         // 背包容量
         int V = 5;
         // 物品体积数组
         int[] v = {1, 2, 3, 4};
         // 物品价值数组
         int[] w = {2, 4, 4, 5};
         
         // 调用方法并输出结果
         System.out.println(maxValue(n, V, v, w));
    }
}

 

 

4.  优化写法

除了二维写法,还有哪些优化手段?根据我从网上搜索到的信息,有以下几种可能的优化手段:

    • 一种是使用一维数组来存储动态规划的状态,即只用一个数组f[j]来表示在容量为j下的最大价值。这样可以节省空间,但是需要注意遍历的顺序。对于完全背包问题,需要正序遍历容量,因为每种物品可以取无限次,所以可以利用已经更新过的状态。对于01背包问题,需要逆序遍历容量,因为每种物品只能取一次,所以要避免重复计算。

    • 另一种是使用二进制优化的方法,即将每种物品拆分成若干个01背包问题。具体的做法是将每种物品的数量用二进制表示,然后按照二进制位的权重将物品分成若干份,每份的数量为2^k,其中k为二进制位的位置。例如,如果某种物品的数量为13,那么可以拆分成1,2,4,6四份,分别对应二进制位1101。这样就将一个完全背包问题转化为了多个01背包问题,然后按照01背包问题的方法求解即可。这样可以减少枚举的次数,提高效率。

    • 还有一种是使用单调队列优化的方法,即利用一个单调递减的队列来维护状态转移过程中的最大值。具体的做法是将每种物品按照价值密度(即价值除以体积)从大到小排序,然后对于每种物品,用一个单调队列来存储其在不同容量下的最大价值,并保持队列中的元素递减。这样就可以在O(1)的时间内找到当前容量下的最大价值,并更新状态。这样可以避免重复计算和比较,提高效率。

 

4.1  使用一维数组来存储动态规划的状态

public class BackpackComplete {
    public static int maxValue(int n, int V, int[] v, int[] w) {
        // 动态规划数组
        int[] dp = new int[V + 1];
        // 初始化第一行为0
        for (int j = 0; j <= V; j++) {
            dp[j] = 0;
        }
        // 状态转移
        for (int i = 1; i <= n; i++) {
            for (int j = v[i - 1]; j <= V; j++) { // 正序遍历容量
                dp[j] = Math.max(dp[j], dp[j - v[i - 1]] + w[i - 1]); // 比较不装和装第i种物品的最大价值
            }
        }
        // 返回最大价值
        return dp[V];
    }
    
    public static void main(String[] args) {
        // 物品数量
        int n = 4;
        // 背包容量
        int V = 5;
        // 物品体积数组
        int[] v = {1, 2, 3, 4};
        // 物品价值数组
        int[] w = {2, 4, 4, 5};
        
        // 调用方法并输出结果
        System.out.println(maxValue(n, V, v, w));
    }
}

4.2  使用二进制优化的方法

import java.util.ArrayList;
import java.util.List;

public class BackpackCompleteBinary {
    public static int maxValue(int n, int V, int[] v, int[] w, int[] s) {
         // 动态规划数组
         int[] dp = new int[V + 1];
         // 初始化第一行为0
         for (int j = 0; j <= V; j++) {
             dp[j] = 0;
         }
         
         // 拆分物品并按照价值密度排序,这里省略排序的过程,假设已经排好序了
         List<Integer> newV = new ArrayList<>(); // 新的物品体积列表
         List<Integer> newW = new ArrayList<>(); // 新的物品价值列表
         
         for (int i = n - 1; i >= 0; i--) { // 假设从大到小排序,所以倒序遍历原数组
             int num = s[i]; // 第i种物品的数量
             for (int k = 1; k <= num; k <<= 1) { // 按照二进制位拆分物品,每次乘以2
                 num -= k; // 减去拆分出来的数量
                 newV.add(v[i] * k); // 加入新的物品体积列表
                 newW.add(w[i] * k); // 加入新的物品价值列表
             }
             if (num > 0) { // 如果还有剩余,就加入剩余的部分作为一个新的物品
                 newV.add(v[i] * num);
                 newW.add(w[i] * num);
             }
         }
         
         // 状态转移,和01背包问题一样,只是物品数量变多了
         for (int i = 0; i < newV.size(); i++) {
             for (int j = V; j >= newV.get(i); j--) { //逆序遍历容量
                 dp[j] = Math.max(dp[j], dp[j - newV.get(i)] + newW.get(i)); // 比较不装和装第i种物品的最大价值
             }
         }
         
          // 返回最大价值
          return dp[V];
    }
    
    public static void main(String[] args) {
         // 物品数量
         int n = 4;
         // 背包容量
         int V = 5;
         // 物品体积数组
         int[] v = {1, 2, 3, 4};
         // 物品价值数组
         int[] w = {2, 4, 4, 5};
         // 物品数量数组
         int[] s = {3, 1, 2, 1};
         
         // 调用方法并输出结果
         System.out.println(maxValue(n, V, v, w, s));
    }
}

4.3   使用单调队列优化的方法

 

import java.util.ArrayDeque;
import java.util.Deque;

public class BackpackCompleteQueue {
    public static int maxValue(int n, int V, int[] v, int[] w) {
         // 动态规划数组
         int[] dp = new int[V + 1];
         // 初始化第一行为0
         for (int j = 0; j <= V; j++) {
             dp[j] = 0;
         }
         
         // 按照价值密度排序,这里省略排序的过程,假设已经排好序了
         
         // 状态转移,利用单调队列维护最大值
         for (int i = n - 1; i >= 0; i--) { // 假设从大到小排序,所以倒序遍历原数组
             Deque<Integer> queue = new ArrayDeque<>(); // 创建一个单调队列,存储下标
             for (int j = 0; j < v[i]; j++) { // 遍历每个余数类别,即j % v[i]相同的容量
                 while (!queue.isEmpty() && j <= V) { // 如果队列不为空且容量不超过上限
                     while (!queue.isEmpty() && dp[j] >= dp[queue.peekLast()]) { // 如果队尾元素对应的价值小于等于当前价值,就弹出队尾元素,保持队列递减
                         queue.pollLast();
                     }
                     queue.offerLast(j); // 将当前下标加入队尾
                     dp[j] = dp[queue.peekFirst()] + (j - queue.peekFirst()) / v[i] * w[i]; // 更新当前容量下的最大价值,等于队首元素对应的价值加上额外放入的物品i的价值
                     j += v[i]; // 容量增加一个物品i的体积
                 }
                 if (!queue.isEmpty() && queue.peekFirst() == j - v[i] * (V / v[i] + 1)) { // 如果队首元素对应的容量已经超过了上限,就弹出队首元素
                     queue.pollFirst();
                 }
             }
         }
         
          // 返回最大价值
          return dp[V];
    }
    
    public static void main(String[] args) {
         // 物品数量
         int n = 4;
         // 背包容量
         int V = 5;
         // 物品体积数组
         int[] v = {1, 2, 3, 4};
         // 物品价值数组
         int[] w = {2, 4, 4, 5};
         
         // 调用方法并输出结果
         System.out.println(maxValue(n, V, v, w));
    }
}

 

标签:背包,int,数组,物品,动态,规划,public,dp
From: https://www.cnblogs.com/shoshana-kong/p/17537325.html

相关文章

  • 统计的系统客观性与动态进化性•Freq频率与Bayes两大学派及争论•统计推断•Bayes学派
    统计的系统客观性:统计数据及其活动不是片面的,而是系统客观反映客观现象。周期的做“总体统计”+随机/按需/周期做“抽样统计”;统计的动态进化性:统计数据及其活动不是静止的,持续的更新(量变)与进化(质变)。先验信息的收集挖掘和加工,数量化,形成"先验分布"并持续进化。p......
  • 将RUP与管理成功的规划集成在一起
    本文来自于RationalEdge:本文概述了如何在业务建模、需求管理和工具支持的重要领域中,将RUP和MSP集成起来处理MSP中的缺口。最终结果是一个更全面的处理方法,分析和指定一个组织的转化,以及软件工具和技术的使用。编者注:下面的文章描述了管理成功的规划(ManagingSuccessfulProg......
  • POJ 2976:Dropping tests 01 分数规划
    DroppingtestsTimeLimit: 1000MS MemoryLimit: 65536KTotalSubmissions: 12291 Accepted: 4293DescriptionInacertaincourse,youtake n tests.Ifyouget ai outof bi questionscorrectontest i,yourcumulativeaverageisdefinedtobe.Gi......
  • BZOJ 4247: 挂饰 背包dp
    4247:挂饰TimeLimit: 10Sec  MemoryLimit: 256MBSubmit: 999  Solved: 387[Submit][Status][Discuss]DescriptionJOI君有N个装在手机上的挂饰,编号为1...N。JOI君可以将其中的一些装在手机上。JOI君的挂饰有一些与众不同——其中的一些挂饰附有可以挂其他挂件......
  • BZOJ 2427: [HAOI2010]软件安装 树形背包
    2427:[HAOI2010]软件安装TimeLimit: 10Sec  MemoryLimit: 128MBSubmit: 1275  Solved: 492[Submit][Status][Discuss]Description现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi。我们希望从中选择一些软件安装到一台磁盘容量为M计算机......
  • BZOJ 1042:[HAOI2008]硬币购物 容斥原理 背包dp
    1042:[HAOI2008]硬币购物TimeLimit: 10Sec  MemoryLimit: 162MBSubmit: 2505  Solved: 1505[Submit][Status][Discuss]Description硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买si的价值的东西。请问每次......
  • python 获取动态库 lib-dynload 路径
    动态库lib-dynload路径python3-c'importrandomasm;print(m.__file__)'参考;https://blog.csdn.net/jaket5219999/article/details/53512071......
  • vue - 动态组件(:is在组件中的使用)
    使用场景:有些场景会需要在两个组件间来回切换,比如Tab界面:我们可以通过Vue的<component>元素和特殊的isattribute实现的:如<!--currentTab改变时组件也改变--><component:is="currentTab"></component>在上面的例子中,被传给:is的值可以是以下几种:被注册的组件......
  • 动态规划 01背包问题 二维转一维过程及理解
     1. 01背包:二维朴素写法 publicstaticintgetMaxValue(int[]weight,int[]values,intw){intn=weight.length;int[][]dp=newint[n+1][w+1];for(inti=1;i<=n;i++){for(intj=1;j<=w;j++){if(j>=we......
  • easyExcel 动态列以及自适应列宽的实现
    easyExcel动态列以及自适应列宽的实现在使用EasyExcel实现动态表头和数据以及自适应列宽时,可以按照以下步骤进行操作:1.动态表头和数据:EasyExcel提供了@ExcelProperty注解来指定对象属性与Excel列之间的映射关系。我们可以通过定义一个包含所有可能出现的列名作为键和对......