【蓝桥杯准备打卡-基础算法笔记DP篇】-4.【分组背包问题】

1.问题描述:

N组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。

每件物品的体积vij,价值是 wij,其中 i 是组号,j是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

ps:分组背包问题 和 多重背包问题 的根本区别 在于 分组背包问题 中 分组背包是每组内选一种, 而多重背包的 每种 选 k个。

2.问题的解决思路:

2.0 思路概要

解决 dp问题的关键任然在于 状态表示和状态计算,这一点和完全背包问题如出一辙。

其实 我们仔细分析 分组背包 和 多重背包 以及 完全背包 之间的关系,我们发现是不断泛化的过程.

分组背包 其实是 最大的定义

为了方便理解我们仔细看看各类背包之间的定义 以及互相概念意义之间互通的转化

分组背包: 分成若干组,每组内有各种价值,体积的 物品,每组选一个装入 背包

多重背包: 分成若干组,每组内有同种价值,体积的 物品,物品数量题目给定,选择k个该物品进入背包

按照分组背包的方式定义

多重背包: 分成若干组,每组内有同种价值,体积的 物品,物品数量题目给定,(将k个同种物品打包成 一个新物品,新物品的体积,价值是原来的k倍)选择k个该物品 进入背包

完全背包: 分成若干组,每组内有同种价值,体积的 物品,物品数量题目无限,选择若干个该物品进入背包

按照多重背包的方式定义

完全背包: 分成若干组,每组内有同种价值,体积的 物品,物品数量题目无限(为了放入背包,实际上只能放入最多V/v[i]个物品),选择若干个该物品进入背包

2.1 状态表示

状态表示 可以 分栏为 集合定义以及集合属性

集合定义: 根据题意,我们规定集合dp【i】【j】表示 所有只从前0到i组物品种选,而且总体积 不大于j的选法

集合属性:我们定义集合的属性值 为 满足上述要求的集合所有选法中,造成背包价值最大的选法。

2.2 状态计算

集合的划分:

我们把集合dp 【i】【j】划分为 k 个子集,分别表示 背包中 所拥有 第 i 组 物品的第 k 个.

子集的表示:

其中,绿色 框 所表示的 子集意义 则为 所有只从前0到i组物品种选,而且总体积 不大于j的所有选法,这一前提下,不包含第i组物品的选法 ,也就是 所有只从前0到i-1组物品种选,而且总体积 不大于j的所有选法,根据集合定义,我们得知 绿色 子集表示为 dp[i-1][j]

最右侧的 紫色框 表示集合 意义为 所有只从前0到i组物品种选,而且总体积 不大于j的所有选法,这一前提下,包含第i组物品选第k个物品 的选法

这里偷懒了 ,和完全背包的文是一样的

我们依然采取曲线救国的方式:从紫色 集合 中 同时去除第 k个第i组 物品,则这个时候可以紫色框 根据 状态定义,可以表示为

dp[i-1][j - v[i][k] ]

而 这个集合的 属性值(所有选法中的背包最大价值)和去除 物品之前,相差了 第i个物品价值的 w[i][k] ,那么紫色 集合 泛指的包含k个第 i种个物品集合的选法中最大价值可以表示成

dp[i-1][j - v[i][k] ]+ w[i][k]

动态转移方程

容易想到 背包中能塞入 第i组物品 的k倍,其不难超过当前规定的背包体积j,满足方程

v[i][k]<=j

显然 原 集合 dp[i][j] = max{dp[i-1][j],dp[i-1][j -v[i][k] ]+w[i][k] | v[i][k]<=j }

3.算法优化

3.1 滚动数组一维化

老生常谈了,因为转移方程只和上一层有关,所以可以滚动数组[自我滚动]优化

dp[j] = max{dp[j],dp[j -v[i][k] ]+w[i][k] | v[i][k]<=j }

注意: 要一维的必须循环逆序,和01背包一个道理,为什么完全背包不一样是正序呢,因为完全背包优化过后的动态转移方程 和当前i组有关,完全背包转移方程没优化也是需要逆序的.

3.2代码展示

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3+5;
int dp[N];
int v1[N],w1[N];
int main()
{
    int n,V;
    cin>>n>>V;
    for(int i=1;i<=n;i++)
    {
        int s;
        cin>>s;
   
        for(int j=1;j<=s;j++)
        {
          
   
          cin>>v1[j]>>w1[j];
     
            
        }
        for(int j=V;j>0;j--)
        {
            for(int k=1;k<=s;k++)
            {
               
                  if(j>=v1[k])
                 dp[j]=max(dp[j],dp[j-v1[k]]+w1[k]);
                
            }
        }
    }
    cout<<dp[V];
   
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值