【算法 | 背包专题】分组背包(解题思路+题单)

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

分组背包

上一节,我们提到了什么是01背包,以及如何求解01背包:

  • 【算法 | 背包专题】01背包(状态定义+状态转移+解题流程+题单)

现在,我们来看分组背包。

分组背包问题是背包问题的一种变形。在这个问题中,物品被分成了若干组,每组中的物品相互冲突,也就是说,每组只能选择一个物品。这与01背包问题的主要区别在于,01背包问题中,每个物品都可以被选择,而在分组背包问题中,每组只能选择一个物品。

求解分组背包问题的基本思路是动态规划。我们可以定义一个二维数组dp[i][j],表示前i组物品,总重量不超过j的情况下的最大价值。然后,我们可以遍历每一组物品,对于每一组中的每一个物品,我们都尝试将其加入背包,更新dp[i][j]的值。

欸,这就是分组背包的求解思路,每一组物品都进行可能性展开,去尝试。

模板题

我们来看这道模板题。

通天之分组背包

题目背景

直达通天路·小 A 历险记第二篇

题目描述

01 01 01 背包问世之后,小 A 对此深感兴趣。一天,小 A 去远游,却发现他的背包不同于 01 01 01 背包,他的物品大致可分为 k k k 组,每组中的物品相互冲突,现在,他想知道最大的利用价值是多少。

输入格式

两个数 m , n m,n m,n,表示一共有 n n n 件物品,总重量为 m m m

接下来 n n n 行,每行 3 3 3 个数 a i , b i , c i a_i,b_i,c_i ai,bi,ci,表示物品的重量,利用价值,所属组数。

输出格式

一个数,最大的利用价值。

样例

样例输入

45 3
10 10 1
10 5 1
50 400 2

样例输出

10

提示

0 ≤ m ≤ 1000 0 \leq m \leq 1000 0m1000 1 ≤ n ≤ 1000 1 \leq n \leq 1000 1n1000 1 ≤ k ≤ 100 1\leq k\leq 100 1k100 a i , b i , c i a_i, b_i, c_i ai,bi,ciint 范围内。

状态定义

对于这道题(分组背包问题),我们使用动态规划来解决,定义一个二维数组 dp[i][j],表示前i组物品,总重量不超过j的情况下的最大价值。

解题思路

  1. 首先获取背包的总重量m和物品的数量n,然后读取每个物品的重量、价值和所属组号,接着,将物品按照组号进行排序(重要)。

  2. 接下来,我们计算出总共有多少个组(用变量group存储起来)。

  3. 初始化dp数组,dp[0][j] = 0,表示没有物品的情况下,任何重量的背包获取的价值都是0(在Java中默认值都是0)。

  4. 遍历每一组物品对于每一组,我们都尝试将组内的每一个物品加入背包,更新dp[i][j]的值。具体来说,如果物品的重量小于或等于j,那么我们可以选择将其加入背包,此时dp[i][j] = max(dp[i-1][j], dp[i-1][j-物品的重量] + 物品的价值)

  5. 最后,dp[group][m]就是我们要求的答案,表示前group组物品,总重量不超过m的情况下获取的最大价值。

参考代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;

public class Main {

    public static int MAXN = 1001;

    public static int MAXM = 1001;

    public static int[][] arr = new int[MAXN][3]; // (体积, 价值, 所属组号)

    public static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StreamTokenizer in = new StreamTokenizer(br);
        PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
        while (in.nextToken() != StreamTokenizer.TT_EOF) {
            m = (int) in.nval;
            in.nextToken();
            n = (int) in.nval;
            for (int i = 1; i <= n; i++) {
                in.nextToken();
                arr[i][0] = (int) in.nval; // 体积
                in.nextToken();
                arr[i][1] = (int) in.nval; // 价值
                in.nextToken();
                arr[i][2] = (int) in.nval; // 所属组号
            }
            // 将物品按照组号进行排序
            Arrays.sort(arr, 1, n + 1, (a, b) -> a[2] - b[2]);
            // 输出计算结果
            out.println(compute());
        }
        out.flush();
        out.close();
        br.close();
    }

    // dp[i][j]: 前i组里面,每组最多挑选一件,在容量不超过j的情况下,最大价值是多少?
    public static int compute() {
    	// 首先我们计算总共有多少个组,编号从1开始
        int group = 1;
        for (int i = 2; i <= n; i++) {
            if (arr[i - 1][2] != arr[i][2]) {
                group++;
            }
        }
        int[][] dp = new int[group + 1][m + 1];
        
        for (int start = 1, end = 2, i = 1; start <= n; i++) {
        	// 第i组的范围是 [start, end),注意,范围是左闭右开的
            while (end <= n && arr[end][2] == arr[start][2]) {
                end++;
            }
            // 当知晓第i组的范围之后,就可以进行动态规划了
            for (int j = 0; j <= m; j++) { // 遍历背包容量
            	// 不选物品
                dp[i][j] = dp[i - 1][j];
                // 每一件物品都试一遍,k指的是物品编号
                for (int k = start; k < end; k++) {
                	// 如果背包容量允许,则尝试
                    if (j >= arr[k][0]) {
                        dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - arr[k][0]] + arr[k][1]);
                    }
                }
            }
            // 要记得更新 start !!!
            start = end++;
        }
        // 最后返回前group组里面,每组最多挑选一件,在容量不超过j的情况下,最大价值是多少?
        return dp[group][m];
    }
}

空间压缩

和 01 背包一样,我们也要对分组背包进行空间压缩,优化空间复杂度。

我们可以观察到,dp[i][j]的值只依赖于dp[i-1][j]dp[i-1][j-arr[k][0]],也就是说,计算dp[i][j]的值时,只需要知道第i-1组的信息,而不需要知道i-2i-3等更早的组的信息。因此,我们可以只用一个一维数组dp[j]来存储每一组的信息,当我们处理完一组物品后,就可以覆盖掉这一组的信息,用来存储下一组的信息。

另外,在遍历所有可能的背包容量,以便计算出每种容量下的最大价值时,我们要注意遍历的顺序(即从后往前遍历)。

这是因为我们是在原地更新dp数组,如果我们从前往后遍历背包容量,那么在计算dp[j]时,dp[j - arr[k][0]]可能已经被更新过了,这就导致我们使用的是错误的值。为了避免这个问题,我们需要从后往前遍历背包容量。这样,在计算dp[j]时,dp[j - arr[k][0]]还没有被更新,我们可以安全地使用它的值。

参考代码如下:

// dp[i]: 每组商品最多挑选一件,在容量不超过i的情况下,最大价值是多少?
public static int compute() {
	int[] dp = new int[m + 1];
	// 同样,第i组的范围是 [start, end),注意,范围是左闭右开的
	int start = 1, end = 2;
	while (start <= n) {
		// 首先,找到当前组的范围
		while (end <= n && arr[end][2] == arr[start][2]) {
			end++;
		}
		// 知道当前组的范围后,就可以开始动态规划了,注意背包容量需要从后往前遍历
		// 因为从前往后遍历的话,会覆盖掉之前的数据,进而影响到后续元素的遍历
		for (int j = m; j >= 0; j--) {
			for (int k = start; k < end; k++) {
				if (j >= arr[k][0]) {
					dp[j] = Math.max(dp[j], dp[j - arr[k][0]] + arr[k][1]);
				}
			}
		}
		// 不要忘了更新 start
		start = end++;
	}
	return dp[m];
}

力扣题单

学完后,我们可以刷几道力扣题巩固一下。文章来源地址https://www.toymoban.com/news/detail-847146.html

题目 题解
1155. 掷骰子等于目标和的方法数 题解
1981. 最小化目标值与所选元素的差 题解
2218. 从栈中取出 K 个硬币的最大面值和 题解

到了这里,关于【算法 | 背包专题】分组背包(解题思路+题单)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【每日算法 && 数据结构(C++)】—— 13 | 求最长自增子序列(解题思路、流程图、代码片段)

    Today’s quote is: \\\"Actions speak louder than words. 今天的一句话是:“行动胜于言辞 求最长递增子序列 最长递增子序列是指在给定序列中,找到一个最长的子序列,使得子序列中的元素按照递增的顺序排列。 例如,对于序列 [1, 3, 2, 5, 4, 7, 6],其中的最长递增子序列可以是 [1, 2, 4,

    2024年02月12日
    浏览(42)
  • 【每日算法 && 数据结构(C++)】—— 01 | 平方值去重统计(解题思路STL法,双指针法、流程图、代码片段)

    “Success is not final, failure is not fatal: It is the courage to continue that counts.” - Winston Churchill (成功并非终点,失败并非致命:真正重要的是继续前行的勇气 - 温斯顿·丘吉尔) 给你一个整数数组,数组中的数可以是正数、负数、零,请实现一个函数,返回这个数组中所有数的平方

    2024年02月12日
    浏览(54)
  • 数据结构学习记录——树习题—Tree Traversals Again(题目描述、输入输出示例、解题思路、解题方法C语言、解析)

    目录 题目描述 输入示例 输出示例 解题思路  解题方法(C语言) 解析 有序的二叉树遍历可以用堆栈以非递归的方式实现。 例如: 假设遍历一个节点数为6的二叉树(节点数据分别为1到6)时, 堆栈操作为:push(1);push(2);push(3);pop();pop();push(4);pop()

    2024年02月07日
    浏览(50)
  • 路由表的更新算法(解题思路)

    某时刻路由器R6的路由表如下表: 第一步:将更新信息的距离+1,把下一路由改为发来更新信息的路由 R4更新表: (黄字为解释) 目的网络 距离 下一跳路由器 net1 3 =2+1 R4 R1在R4的下一跳 net2 5 =4+1 R4 R2在R4的下一跳 net3 4 =3+1 R4 R9在R4的下一跳 第二步:与原表对比 R6的原表: 目

    2024年02月11日
    浏览(82)
  • 动态规划详解(完结篇)——如何抽象出动态规划算法?以及解题思路

    今天直接开始讲解 FIRST:如何抽象出动态规划算法? 这个问题,困扰了无数代OIER,包括本蒟蒻 在比赛的时候,看一道题,怎么想到他是什么算法的呢? 这就需要抽象能力 而不同的算法,往往有着不同的特点 就来说说动态规划的题目特点 通过遍历,能够把所有的情况考虑到

    2023年04月08日
    浏览(40)
  • 【Unity之c#专题篇】—核心章题单实践

    👨‍💻个人主页 :@元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏 : unityc#专题篇习题 核心章知识点详解入口 🅰️ 题单来自:B站唐老狮 实践经验 : 1.类是对象,清楚类的本质 2.对象和对象之间可作为参

    2024年02月05日
    浏览(38)
  • 【DFS专题】深度优先搜索 “暴搜”优质题单推荐 10道题(C++ | 洛谷 | acwing)

    【DFS专题】优质题单推荐 10道题(C++ | 洛谷 | acwing) 来自b站大佬的题单 题单链接 每个位置选什么数 与全排列的差别就是第二个for循环开始的位置,换句话说就是每个数该放什么位置。 参数 : 前u个数 选 or 不选 的 需要保存第x位置的状态的时候就需要用st数组来存状态 i

    2023年04月08日
    浏览(50)
  • Java数据结构与算法----动态规划(背包篇)

    1.1.算法思路 0/1背包是动态规划、背包问题中最经典的问题啦!它主要的问题是: 给定n种物品、这n种物品的重量分别是,价值分别是 ,而你有一个容量为C的背包,请问如何求出所能拿的最大价值呢? 对于动态规划,我们先需要找到一条推导公式,然后确定边界: 我们设

    2024年02月07日
    浏览(50)
  • 数据结构和算法专题---3、失效算法与应用

    本章我们会对失效算法做个简单介绍,包括常用的失效算法(先来先淘汰(FIFO)、最久未用淘汰(LRU)、最近最少使用(LFU))的概述、实现方式、典型场景做个说明。 失效算法常见于缓存系统中。因为缓存往往占据大量内存,而内存空间是相对昂贵,且空间有限的,那么

    2024年02月04日
    浏览(44)
  • 数据结构和算法专题---4、限流算法与应用

    本章我们会对限流算法做个简单介绍,包括常用的限流算法(计数器、漏桶算法、令牌桶案发、滑动窗口)的概述、实现方式、典型场景做个说明。 限流是对系统的一种保护措施。即限制流量请求的频率(每秒处理多少个请求)。一般来说,当请求流量超过系统的瓶颈,则丢

    2024年02月04日
    浏览(49)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包