《分组背包》基础概念

一、算法概述

  1. 问题定义:分组背包问题是背包问题的经典变种,给定 n n n 组物品,每组包含若干个物品,每个物品有重量 w w w 和价值 v v v。每组中最多选择一个物品放入容量为 V V V 的背包,目标是在不超过背包容量的前提下,使背包内物品的总价值最大。
  2. 与其他背包问题的区别:相比01背包(每个物品独立选或不选)和多重背包(同一种物品有数量限制),分组背包强调"组"的概念,每组内物品互斥(最多选一个),是更复杂的组合决策问题,常用于资源分配、任务调度等场景。

二、算法思路

  1. 核心思想:通过动态规划方法,将问题分解为"考虑前 i i i 组物品,背包容量为 j j j 时的最大价值"这类子问题,利用子问题的解构建最终答案。
  2. 状态定义:定义二维数组 dp[i][j] 表示考虑前 i i i 组物品,背包容量为 j j j 时能获得的最大价值。
  3. 状态转移方程:对于第 i i i 组物品,有两种选择:
    • 不选第 i i i 组中的任何物品:dp[i][j] = dp[i-1][j]
    • 选第 i i i 组中的第 k k k 个物品(若容量允许):dp[i][j] = max(dp[i][j], dp[i-1][j-w[i][k]] + v[i][k])
      最终状态转移方程为:dp[i][j] = max(dp[i-1][j], max{dp[i-1][j-w[i][k]] + v[i][k] | 第k个物品在第i组中且j≥w[i][k]})
  4. 遍历顺序:先遍历物品组,再遍历背包容量,最后遍历组内物品,确保每组物品只选一个。

三、伪代码实现

1. 数据结构定义

结构体 Item:
    cnt: 整数  # 该组物品数量
    w[1..maxc]: 整数数组  # 重量数组
    v[1..maxc]: 价值数组  # 价值数组

2. 分组背包主算法

算法:分组背包
输入:物品组数 n,背包容量 V,物品组数组 items[1..n],状态数组 dp[0..n][0..V]
输出:背包能装下物品的最大总价值

function KnapsackGroup(n, V, items[]):
    // 初始化 dp 数组,容量为 0 时价值为 0,其余初始化为负无穷
    for j from 1 to V:
        dp[0][j] = -∞
    dp[0][0] = 0

    // 遍历每组物品
    for i from 1 to n:
        // 遍历背包容量
        for j from 0 to V:
            // 初始状态:不选第 i 组物品时的价值
            dp[i][j] = dp[i-1][j]
            // 遍历第 i 组中的每个物品
            for k from 0 to items[i].cnt - 1:
                if j >= items[i].w[k]:
                    // 尝试选择第 i 组的第 k 个物品
                    temp = dp[i-1][j - items[i].w[k]] + items[i].v[k]
                    // 更新最大价值
                    dp[i][j] = max(dp[i][j], temp)

    // 找到容量 0 到 V 中的最大价值
    maxValue = -∞
    for j from 0 to V:
        maxValue = max(maxValue, dp[n][j])
    return maxValue

// 主程序
输入 n, V
for i from 1 to n:
    输入 items[i].cnt
    for j from 0 to items[i].cnt - 1:
        输入 items[i].w[j], items[i].v[j]
输出 KnapsackGroup(n, V, items)

四、算法解释

1. 初始化阶段

  • dp[0][j](不考虑任何物品时)初始化为负无穷(容量大于0时无解),dp[0][0] 初始化为0(容量为0时价值为0)。

2. 状态转移过程

  • 遍历物品组:按顺序处理每组物品,确保前 i − 1 i-1 i1 组的状态已计算完成。
  • 遍历背包容量:从小到大或从大到小遍历容量(此处为从小到大,因为每组物品互斥,无需担心重复选择)。
  • 遍历组内物品:对每组中的每个物品,若背包能容纳该物品,则计算选择该物品后的价值,并与不选该组物品的价值比较,取较大值。

3. 示例演示

  • 假设背包容量 V = 5 V = 5 V=5,有2组物品:
    • 第1组(2个物品):物品1( w = 2 , v = 3 w=2, v=3 w=2,v=3)、物品2( w = 3 , v = 4 w=3, v=4 w=3,v=4
    • 第2组(1个物品):物品3( w = 4 , v = 5 w=4, v=5 w=4,v=5
  • 处理第1组
    • 容量 j = 2 j=2 j=2 时,选物品1,dp[1][2] = 3
    • 容量 j = 3 j=3 j=3 时,选物品2,dp[1][3] = 4
    • 容量 j = 5 j=5 j=5 时,选物品1+物品2(总重量5),dp[1][5] = 3+4=7
  • 处理第2组
    • 容量 j = 4 j=4 j=4 时,选物品3,dp[2][4] = 5
    • 容量 j = 5 j=5 j=5 时,比较不选第2组(7)和选物品3( d p [ 1 ] [ 1 ] + 5 = − ∞ + 5 = − ∞ dp[1][1]+5= -∞+5=-∞ dp[1][1]+5=+5=),最终 dp[2][5]=7
  • 最终结果:遍历 dp[2][j] 找到最大值7。

4. 优化说明

  • 空间优化:若使用一维数组 dp[j],需从大到小遍历容量,避免同一组物品被重复选择。状态转移方程为:
    for i from 1 to n:
        for j from V down to 0:
            for k from 0 to items[i].cnt - 1:
                if j >= items[i].w[k]:
                    dp[j] = max(dp[j], dp[j - items[i].w[k]] + items[i].v[k])
    

五、复杂度分析

  1. 时间复杂度
    • 算法包含三重循环:遍历物品组 O ( n ) O(n) O(n)、遍历背包容量 O ( V ) O(V) O(V)、遍历组内物品 O ( c m a x ) O(c_{max}) O(cmax) c m a x c_{max} cmax 为每组物品的最大数量),因此总时间复杂度为 O ( n × V × c m a x ) O(n \times V \times c_{max}) O(n×V×cmax)
  2. 空间复杂度
    • 使用二维数组 dp[i][j] 时,空间复杂度为 O ( n × V ) O(n \times V) O(n×V);使用一维数组优化后,空间复杂度降为 O ( V ) O(V) O(V)
  3. 与其他背包问题的对比
    • 01背包: O ( n × V ) O(n \times V) O(n×V),每组1个物品
    • 多重背包: O ( n × log ⁡ c × V ) O(n \times \log c \times V) O(n×logc×V),每组同一种物品有数量限制
    • 分组背包: O ( n × V × c m a x ) O(n \times V \times c_{max}) O(n×V×cmax),每组内物品互斥且类型不同

  本文为作者(英雄哪里出来)在抖音的独家课程《英雄C++入门到精通》、《英雄C语言入门到精通》、《英雄Python入门到精通》三个课程的配套文字讲解,如需了解算法视频课程,请移步 作者本人 的抖音直播间。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

英雄哪里出来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值