HJ16 购物单 - 分组背包问题求解

这篇具有很好参考价值的文章主要介绍了HJ16 购物单 - 分组背包问题求解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

HJ16 购物单 - 分组背包问题求解

题目链接参考 HJ16 购物单_牛客题霸_牛客网

1、温故知新

这道题需要通过动态规划来求解,首先先通过ChatGPT了解下如何利用动态规划求解01背包问题和完全背包问题,以下是ChatGPT的答案

  1. 动态规划是什么?

动态规划(Dynamic Programming,DP)是一种常用的算法思想,用于解决多阶段决策问题,以及最优化问题。动态规划通过将问题分解成多个子问题,并在解决子问题的过程中存储中间结果,从而避免重复计算,提高算法效率。

动态规划通常用于求解具有以下性质的问题:

  1. 最优子结构:一个问题的最优解包含其子问题的最优解。

  2. 重叠子问题:问题可以被分解为多个重叠的子问题,这些子问题的解可以被重复利用。

动态规划算法通常分为两个阶段:设计阶段和实现阶段。在设计阶段,需要分析问题的特点,设计状态表示方法,定义状态转移方程,确定边界条件。在实现阶段,需要按照设计阶段的思路,通过循环或递归的方式,求解问题的最优解。

常见的动态规划问题包括背包问题、最长公共子序列问题、最短路径问题、最大子段和问题等。动态规划算法在实际应用中具有广泛的应用,例如图像识别、自然语言处理、数据挖掘等领域。

  1. 用java实现动态规划算法来解决01背包问题?

下面是使用Java实现动态规划算法来解决背包问题的代码示例:

public class Knapsack {

    public static void main(String[] args) {
        int[] values = {60, 100, 120};  // 物品价值
        int[] weights = {10, 20, 30};  // 物品重量
        int capacity = 50;  // 背包容量
        int n = values.length;  // 物品个数
        int[][] dp = new int[n+1][capacity+1];  // 动态规划数组

        // 填充动态规划数组
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= capacity; j++) {
                if (weights[i-1] > j) {
                    dp[i][j] = dp[i-1][j];  // 背包容量不足,无法放入第i个物品
                } else {
                    dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weights[i-1]] + values[i-1]);  // 选择价值更大的物品
                }
            }
        }

        // 输出最大价值
        System.out.println("Max value: " + dp[n][capacity]);
    }
}

在上述代码中,我们首先定义了三个数组:values数组存储物品的价值,weights数组存储物品的重量,capacity变量存储背包的容量。然后我们定义了一个dp二维数组来存储动态规划过程中的中间结果。

接下来,我们通过两个嵌套循环来遍历每一个物品和每一个容量,使用动态规划的思想,根据当前物品是否放入背包,来更新dp数组。具体来说,如果当前物品的重量大于当前背包的容量,那么当前物品无法放入背包,所以背包的最大价值和上一个物品时背包的最大价值是一样的;如果当前物品的重量小于等于当前背包的容量,那么我们需要比较选择当前物品和不选择当前物品两种情况下的背包最大价值,选择其中更大的一个。

最后,我们输出dp[n][capacity],即为背包中物品的最大价值。

值得注意的是,这里我们使用的是0-1背包问题,即每个物品要么放入背包,要么不放入。如果是完全背包问题,即每个物品可以无限次放入背包,那么在动态规划数组更新时需要修改为dp[i][j] = Math.max(dp[i-1][j], dp[i][j-weights[i-1]] + values[i-1]);

  1. 完全背包问题是什么,如何通过修改上面的代码来实现?

完全背包问题是指:有一组物品,每种物品都有无限件可用,给定一个背包容量,问怎么装能够使背包的总价值最大?

0/1背包问题不同,完全背包问题中每种物品可以选择多次放入背包,所以在状态转移方程中,需要将 j-w[i] 这个状态从之前的状态中取到最大值,而不是只取一次。

针对完全背包问题,可以对上面的 0/1 背包问题代码进行一些修改,将第二层循环从小到大遍历,这样可以保证在计算 f[j-w[i]]+v[i]时,已经算过了 j-w[i]这个状态的所有可能的取值,不会漏算任何可能的方案。

修改后的代码如下所示:

public static int knapsack(int[] w, int[] v, int c) {
    int n = w.length;
    int[] f = new int[c+1];
    for (int i = 0; i < n; i++) {
        for (int j = w[i]; j <= c; j++) {
            f[j] = Math.max(f[j], f[j-w[i]]+v[i]);
        }
    }
    return f[c];
}

其中,w 数组表示每个物品的重量,v 数组表示每个物品的价值,c 表示背包的容量,返回值为背包能够装下的最大价值。

2、尝试过程

对于HJ16 购物单_牛客题霸_牛客网这道题,一开始我想了两小时也写不出解决方案(错误代码如下,仅供比较参考)

package main.category.动态规划.level2.HJ16_购物单;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main_error {

    public static void updateMainPart(int[][] dp, int[] mainPart_flag, int[] val, int i, int j, Map<Integer,Integer> map){
        int max = dp[i][j];
        while(i > 0 && j > 0){
            if(max > dp[i-1][j]){
                mainPart_flag[map.get(i)] = 1;  //当前第i个主件已购入,map.get(i)是对主件序号进行重映射
                j = j - val[i]; //回溯到没有购入第i个主件的状态
                if(j > 0) max = dp[i][j];
            }else{
                i = i - 1; //回溯到上一个商品购入时的状态
            }
        }
    }

    //元素整体向后移动
    public static void shift(int[] arr, int val, int i){
        if(i != arr.length - 1){
            for (int j = arr.length - 1; j > i ; j--) {
                arr[j] = arr[j-1];
            }
            arr[i] = val;
        }
    }

    //主件在前,附件在后,返回最后一个主件的位置
    public static int sort(int[] v, int[] w, int[] q, int v_i, int w_i, int q_i, int index){
        if(q_i == 0){
            index += 1;
            shift(v,v_i,index);
            shift(w,w_i,index);
            shift(q,q_i,index);
        }
        return index;
    }

    //对于分组背包问题,不能直接用0/1背包问题来求解
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String[] tokens = scanner.nextLine().split(" ");

        int N = Integer.parseInt(tokens[0]),m = Integer.parseInt(tokens[1]);  //N表示总钱数,m表示可购买的物品个数。
        int[] v = new int[m + 1];  //该物品的价格
        int[] w = new int[m + 1];  //该物品的权重
        int[] q = new int[m + 1];  //该物品是主件还是附件:如果q为0则为主件,主件编号为索引值;如果q>0则为附件,附件编号为q
        HashMap<Integer,Integer> map = new HashMap<Integer, Integer>();

        int i = 1;
        //对商品进行排序,先购入主件,再购入附件
        int index = 0;
        while(i <= m){
            String[] temp = scanner.nextLine().split(" ");
            int v_i = Integer.parseInt(temp[0]);
            int w_i = Integer.parseInt(temp[1]);
            int q_i = Integer.parseInt(temp[2]);
            v[i] = v_i;
            w[i] = w_i;
            q[i] = q_i;

            map.put(index,i);
            index = sort(v, w, q, v_i, w_i, q_i, index);  //由于主件编号与原序列的索引号有关,如果要排序,需要先进行重映射

            i++;
        }

        /**
         * 约束条件:
         *    1) 如果要买归类为附件的物品,必须先买该附件所属的主件,且每件物品只能购买一次
         *    2)每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。
         *    3) 满意度计算公式:v[j_1]*w[j_1]+v[j_2]*w[j_2]+ … +v[j_k]*w[j_k]v[j]
         * 理解:在0-1背包问题上加了上面的约束条件
         * 动态转移方程:
         *      假设当前要加入的物体价值为val,则dp[j][k] = max(dp[j-1][k], dp[j][k-val] + val)
         *      如果加入的是主件,则可以直接加入;如果加入的是附件,则需要判断对应主件是否存在
         */
        int[][] dp = new int[m + 1][N + 1];
        int[] mainPart_flag = new int[m + 1];  //表示当前状态的主件是否已购入:0表示没有,1表示有
        for (i = 1; i <= m; i++) {
            for (int j = 1; j <= N; j++) {
                //先判断是否为主件
                if(q[i] == 0){
                    //当前主件只能购买一次
                    if (v[i] > j || mainPart_flag[i] == 1){
                        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);  //使用不包含第i物品/包含第i物品的最新状态:
                    }
                    else {
                        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - v[i]] + (v[i] * w[i]));
                    }
                }else{
                    //在加入附件之前,先对dp进行回溯,更新主件的购买信息
                    updateMainPart(dp, mainPart_flag, v, i, j,map);

                    //判断该附件对应主件是否已经购买,如果已购买则可以选择加入该商品
                    if(mainPart_flag[q[i]] == 1){
                        if(v[i] > j){
                            dp[i][j] = dp[i-1][j];  //使用不包含当前物品的最新状态
                        }else{
                            dp[i][j] = Math.max(dp[i-1][j],dp[i][j - v[i]] + (v[i] * w[i]));
                        }
                    }
                }
            }
        }
        System.out.println(dp[m][N]);
    }
}

而且越改越吃力,最后放弃了。

3、解决方案

后来参考了题解 | #购物单#_牛客博客,他用分组背包的解法来解这道题,即先根据题意,把能一次性放入包中的方案划分成四种情况

  1. 仅加入一个主件;
  2. 加入主件和第一个附件;
  3. 加入主件和第二个附件;
  4. 加入主件和两个附件;

通过ChatGPT了解了分组背包的问题描述和解法:分组背包如何求解?

分组背包是指一组物品,其中每个物品都被划分为不同的组别,每个组别中的物品只能选择一件放入背包中,问如何选择物品使得背包的总价值最大。

相比于 0/1 背包问题和完全背包问题,分组背包问题中的物品被分为不同的组别,这给问题的求解带来了一些不同的思路。我们可以将物品按照组别分别处理,针对每个组别使用类似于 0/1 背包问题的状态转移方程,最后将各个组别的结果进行合并。

具体的,可以使用一个二维数组 f[i][j] 来表示前 i 个组的物品放入容量为 j 的背包中所能获得的最大价值,其中 i 表示组别的个数,j 表示背包的容量。每个组别中的物品只能选择一件放入背包中,因此转移方程中需要对每个组别进行单独处理:

f[i][j] = max{f[i-1][j-kw[i][j]]+kv[i][j]},其中 k 为第 i 组中物品的数量,w[i][j] 表示第 i 组中第 j 个物品的重量,v[i][j] 表示第 i 组中第 j 个物品的价值。

因此,我们需要在循环中分别枚举每个组别,然后对每个组别中的物品进行背包问题的求解。

代码如下所示:

public static int groupKnapsack(int[][] w, int[][] v, int[] c) {
    int n = w.length;
    int m = c.length;
    int[][] f = new int[n+1][];
    for (int i = 0; i <= n; i++) {
        f[i] = new int[m+1];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            f[i][j] = f[i-1][j];
            for (int k = 1; k <= w[i-1].length; k++) {
                if (j >= w[i-1][k-1] && f[i][j] < f[i-1][j-k*w[i-1][k-1]]+k*v[i-1][k-1]) {
                    f[i][j] = f[i-1][j-k*w[i-1][k-1]]+k*v[i-1][k-1];
                }
            }
        }
    }
    return f[n][m];
}

其中,w 数组表示每个物品的重量,v 数组表示每个物品的价值,c 数组表示每个组别的背包容量,返回值为背包能够装下的最大价值。

参考了题解 | #购物单#_牛客博客提供的java实现方法,并在其中加了一些注释,代码如下:文章来源地址https://www.toymoban.com/news/detail-420250.html

package main.category.动态规划.level2.HJ16_购物单;

import java.util.*;
public class Main {
    /**
     * 参考答案:https://blog.nowcoder.net/n/477ed49f893941bbb20ef8a0651acfd0?f=comment
     * @param args
     */
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        /**
         * 题目的要求是:
         * 1) 如果要买归类为附件的物品,必须先买该附件所属的主件,且每件物品只能购买一次
         * 2)每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。
         * 3) 满意度计算公式:v[j_1]*w[j_1]+v[j_2]*w[j_2]+ … +v[j_k]*w[j_k]v[j]
         */
        while (sc.hasNextLine()) {
            int money = sc.nextInt();
            int m = sc.nextInt();
            sc.nextLine();
            money /= 10;   //为了减少dp大小,将money和单价都除以10

            /**
             * 由于题目要求:
             * 每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件,因此在每行输入中,输入某个主件的附件最多为两个,
             * 因此可以用一个int[m+1][3]的二维数组,绑定组件与附件之间的关系
             */
            int[][] prices = new int[m+1][3];
            int[][] weights = new int[m+1][3];
            for (int i = 1; i <= m; i++) {
                int a = sc.nextInt();
                int b = sc.nextInt();
                int c = sc.nextInt();
                a /= 10;//price  //单价除以10
                b = b * a;//weight
                if (c == 0) {
                    // 主件
                    prices[i][0] = a;
                    weights[i][0] = b;
                } else if (prices[c][1] == 0) {
                    // 附件1
                    prices[c][1] = a;
                    weights[c][1] = b;
                } else {
                    // 附件2
                    prices[c][2] = a;
                    weights[c][2] = b;
                }
                sc.nextLine();
            }
            int[][] dp = new int[m+1][money+1];
            for (int i = 1; i <= m; i++) {
                for(int j = 1; j <= money; j++) {
                    int a = prices[i][0];
                    int b = weights[i][0];
                    int c = prices[i][1];
                    int d = weights[i][1];
                    int e = prices[i][2];
                    int f = weights[i][2];

                    /**
                     * 分组背包问题:可以进行多次的0-1背包求解,其中每次放入背包的方案由主件和0、1、2个附件得到
                     */
                    dp[i][j] = j - a >= 0 ? Math.max(dp[i-1][j], dp[i-1][j-a] + b) : dp[i-1][j];  //单买主件
                    dp[i][j] = j-a-c >= 0 ? Math.max(dp[i][j], dp[i-1][j-a-c] + b + d):dp[i][j];  //买主件 + 附件1
                    dp[i][j] = j-a-e >= 0 ? Math.max(dp[i][j], dp[i-1][j-a-e] + b + f):dp[i][j];  //买主件 + 附件2
                    dp[i][j] = j-a-c-e >= 0 ? Math.max(dp[i][j], dp[i-1][j-a-c-e] + b +d + f):dp[i][j];  //买主件 + 附件1 + 附件2
                }
            }
            System.out.println(dp[m][money] * 10);  //乘上10得到最终满意度
        }
    }
}

到了这里,关于HJ16 购物单 - 分组背包问题求解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【LuoGU 1273】有线电视网——树上分组背包问题

    某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。 从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播

    2024年02月16日
    浏览(39)
  • 算法分析与设计——动态规划求解01背包问题

    假设有四个物品,如下图,背包总容量为8,求背包装入哪些物品时累计的价值最多。 我们使用动态规划来解决这个问题,首先使用一个表格来模拟整个算法的过程。 表格中的信息表示 指定情况下能产生的最大价值 。例如, (4, 8)表示在背包容量为8的情况下,前四个物品的最

    2024年02月04日
    浏览(70)
  • 0-1背包问题的多种算法求解(C语言)

            1.问题描述         有一个容量为V的背包,还有n个物体。现在忽略物体实际几何形状,我们认为只要背包的剩余容量大于等于物体体积,那就可以装进背包里。每个物体都有两个属性,即体积w和价值v。 使物品装入背包的价值最大。         2.解决思路与

    2024年02月10日
    浏览(38)
  • 算法设计与分析实验二:动态规划法求解TSP问题和01背包问题

    【实验内容】 (1)tsp问题:利用动态规划算法编程求解TSP问题,并进行时间复杂性分析。 输入:n个城市,权值,任选一个城市出发; 输出:以表格形式输出结果,并给出向量解和最短路径长度。 (2)01背包问题:利用动态规划算法编程求解0-1背包问题,并进行时间复杂性分

    2024年02月03日
    浏览(58)
  • C++ 算法主题系列之集结0-1背包问题的所有求解方案

    背包问题是类型问题,通过对这一类型问题的理解和掌握,从而可以归纳出求解此类问题的思路和模板。 背包问题的分类有: 0-1 背包问题,也称为不可分割背包问题。 无限背包问题。 判定性背包问题. 带附属关系的背包问题。 双背包求最优值. 构造三角形问题. 带上下界限

    2024年02月01日
    浏览(29)
  • 回溯法,分支限界法,动态规划法求解0-1背包问题(c++)

    问题描述 给定n种物品和一背包。物品i的重量是wi0,其价值为vi0,背包的容量为c。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大? 搜索空间: 子集树(完全二叉树) 约束函数(进包用): 如果当前背包中的物品总重量是cw,前面k-1件物品都已经决定是否进包,那

    2024年02月10日
    浏览(58)
  • 【算法 | 背包专题】分组背包(解题思路+题单)

    上一节,我们提到了什么是01背包,以及如何求解01背包: 【算法 | 背包专题】01背包(状态定义+状态转移+解题流程+题单) 现在,我们来看分组背包。 分组背包问题是背包问题的一种变形。在这个问题中, 物品被分成了若干组 ,每组中的物品相互冲突,也就是说, 每组只

    2024年04月11日
    浏览(39)
  • [动态规划] 分组背包

     开局思路            1.对dp[N] 的涵义进行定义         2.递推公式         3.初始化(此题不用)         4.遍历 1.dp[i][j]的定义:从1 - n组的物品里 选出总体积不超过j的 总价值。 2地推公式:dp[i][j] = max(dp[i][j], dp[i-1][j - v[i][k]] + w[i][k]);                  如若装入

    2024年02月21日
    浏览(34)
  • 16 - Workbench分析类型与通用求解设置

    隐式分析、显式分析   静力分析 模态分析 反应谱分析 谐响应分析 特征值屈曲分析 瞬态分析    线性分析 非线性分析 材料非线性分析 几何非线性分析 接触非线性分析  荷载步、荷载子步、时间 Time、Nsubst、Deltim、Autots 有的载荷不是直接加在上面,而是各渐变的过程。 除

    2024年02月04日
    浏览(75)
  • 16、python中dataframe的合并行/列、分组与聚合、行索引

    1、合并行/列 合并行:t1.join(t2)相当于t1左关联t2,通过行索引关联,保留t1、t2全部字段,t1、t2列重复会报错 合并列:t1.merge(t2,left_on=column1,right_on=column2,how=‘inner’),t1连接t2,通过t1的field1与t2的field2字段连接,有相同的字段可以通过on指定,默认how为inner内连接取交集,ou

    2024年02月08日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包