GESP6级C++考试语法知识(五十三、动态规划----背包问题(六、分组背包)


第六课《宝石分类挑战赛------分组背包》


🎒故事开始:宝石王国大赛

阿宝已经学会了:

✅ 01背包

每件物品最多选一次

✅ 完全背包

每件物品无限选

✅ 多重背包

每件物品有固定数量


1、这一天,宝石王国举行了一场盛大的比赛:

💎 宝石分类挑战赛


2、国王宣布:

勇士们!

你们可以挑选装备参加比赛!

但是每个类别只能选择一种!


3、阿宝来到宝石仓库。

发现宝石被分成了许多组。


(1)武器组

名称 重量 价值
木剑 1 2
铁剑 2 4
圣光剑 3 7

(2)头盔组

名称 重量 价值
布帽 1 1
铁盔 2 3

(3)鞋子组

名称 重量 价值
草鞋 1 2
魔法靴 2 5

4、国王说:

武器只能选一个!

头盔只能选一个!

鞋子只能选一个!


5、阿宝发现:

这和以前的背包完全不同!


第一幕:什么是分组背包?

以前:


1、01背包

(1)每件物品:

复制代码
选
不选

互不影响。


(2)例如:

复制代码
宝剑
盾牌
药水

都能同时拿。


2、而现在:


(1)武器组:

复制代码
木剑
铁剑
圣光剑

(2)只能选:

复制代码
其中一个

(3)不能:

复制代码
木剑+铁剑


(4)不能:

复制代码
铁剑+圣光剑


(5)只能:

复制代码
木剑

复制代码
铁剑

复制代码
圣光剑

复制代码
一个都不选

3、这就是:

🌟分组背包


第二幕:分组背包定义

1、分组背包:

物品被分成若干组

每组最多选一个


2、例如:


(1)武器组

选一个


(2)头盔组

选一个


(3)鞋子组

选一个


3、🌟口诀

复制代码
每组最多挑一个,
组内物品不能叠。

第三幕:先看一个小例子

1、背包容量:

复制代码
4

2、两组物品:


第一组

重量 价值
1 2
2 4

第二组

重量 价值
1 3
3 5

问:

最大价值是多少?


第四幕:状态定义

1、仍然定义:

复制代码
dp[i][j]

表示:

前 i 组物品

容量 j

最大价值


2、注意!

(1)这里的:

复制代码
i

(2)不再表示:

复制代码
前i件物品

(3)而是:

复制代码
前i组物品

3、🌟这是最容易错的地方

(1)01背包:

复制代码
dp[i][j]

前 i 件物品


(2)分组背包:

复制代码
dp[i][j]

前 i 组物品


第五幕:状态转移

1、来到第 i 组。


2、这一组有:

复制代码
若干物品

3、怎么办?


全部试一遍!


4、例如:

(1)武器组:

复制代码
木剑
铁剑
圣光剑

(2)对于容量 j:

尝试:

复制代码
选木剑

尝试:

复制代码
选铁剑

尝试:

复制代码
选圣光剑

尝试:

复制代码
一个不选

谁更优选谁。


5、🌟状态转移公式

(1)设:

复制代码
k

表示组内某个物品。


(2)重量:

复制代码
w[i][k]

(3)价值:

复制代码
v[i][k]

(4)那么:

复制代码
dp[i][j]
=
max(
dp[i][j],
dp[i-1][j-w[i][k]]
+
v[i][k]
)

(5)含义:

选中了这一组里的某个物品。


第六幕:手算DP表

1、容量:

复制代码
4

2、第一组:

重量 价值
1 2
2 4

初始化:

i\j 0 1 2 3 4
0 0 0 0 0 0

处理第一组。


容量1:

选:

复制代码
重量1
价值2

得到:

复制代码
2

容量2:

选:

复制代码
重量2
价值4

得到:

复制代码
4

容量3:

仍然选价值更大的:

复制代码
4

容量4:

复制代码
4

表格:

i\j 0 1 2 3 4
0 0 0 0 0 0
1 0 2 4 4 4

第二组:

重量 价值
1 3
3 5

继续更新。


容量4:

选:

复制代码
第一组重量2价值4
+
第二组重量1价值3
=
7

或者:

复制代码
第一组重量1价值2
+
第二组重量3价值5
=
7

答案:

复制代码
7

最终:

复制代码
dp[2][4]
=
7

第七幕:三层循环结构

1、分组背包最经典结构:


(1)第一层

复制代码
for(i)

枚举组


(2)第二层

复制代码
for(j)

枚举容量


(3)第三层

复制代码
for(k)

枚举组内物品


2、模板:

复制代码
for(i)
{
    for(j)
    {
        for(k)
        {
        }
    }
}

3、参考程序:

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;

int dp[105][1005];

int w[105][105];
int v[105][105];

int cnt[105];

int main()
{
    int n,m;

    cin>>n>>m;

    for(int i=1;i<=n;i++)
    {
        cin>>cnt[i];

        for(int j=1;j<=cnt[i];j++)
        {
            cin>>w[i][j]
                >>v[i][j];
        }
    }

    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            dp[i][j]=dp[i-1][j];

            for(int k=1;k<=cnt[i];k++)
            {
                if(j>=w[i][k])
                {
                    dp[i][j]
                    =
                    max(
                        dp[i][j],
                        dp[i-1][j-w[i][k]]
                        +
                        v[i][k]
                    );
                }
            }
        }
    }

    cout<<dp[n][m];

    return 0;
}

第八幕:压缩成一维进行优化

1、现在开始优化。


(1)压缩成:

复制代码
dp[j]

(2)如果正序:

复制代码
for(j=0;j<=m;j++)

(3)可能发生:

复制代码
同一组选两件

(4)例如:

武器组:

复制代码
木剑
铁剑

更新木剑后。

又更新铁剑。

结果:

复制代码
木剑+铁剑

一起选上了!


违反规则!


所以:

还是必须倒序。


2、🌟标准一维转移

复制代码
for(int i=1;i<=n;i++)
{
    for(int j=m;j>=0;j--)
    {
        for(int k=1;k<=cnt[i];k++)
        {
            if(j>=w[i][k])
            {
                dp[j]
                =
                max(
                    dp[j],
                    dp[j-w[i][k]]
                    +
                    v[i][k]
                );
            }
        }
    }
}

3、完整一维参考程序

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;

int w[105][105];
int v[105][105];
int cnt[105];

int dp[1005];

int main()
{
    int n,m;

    cin >> n >> m;

    for(int i=1;i<=n;i++)
    {
        cin >> cnt[i];

        for(int j=1;j<=cnt[i];j++)
        {
            cin >> w[i][j] >> v[i][j];
        }
    }

    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=0;j--)
        {
            for(int k=1;k<=cnt[i];k++)
            {
                if(j >= w[i][k])
                {
                    dp[j] = max(
                        dp[j],
                        dp[j-w[i][k]] + v[i][k]
                    );
                }
            }
        }
    }

    cout << dp[m] << endl;

    return 0;
}

🌟更容易理解的写法

很多竞赛书会这样写:

cpp 复制代码
for(int i=1;i<=n;i++)
{
    int old[1005];

    for(int j=0;j<=m;j++)
        old[j]=dp[j];

    for(int j=0;j<=m;j++)
    {
        for(int k=1;k<=cnt[i];k++)
        {
            if(j>=w[i][k])
            {
                dp[j]=max(
                    dp[j],
                    old[j-w[i][k]]+v[i][k]
                );
            }
        }
    }
}

(1)这里:

复制代码
old[]

表示:

复制代码
上一组结束后的状态

(2)于是转移就变成:

cpp 复制代码
当前组  =  从上一组转移

(3)即:

复制代码
dp[j]
=
max(
dp[j],
old[j-w]+v
)

这与二维写法:

复制代码
dp[i][j]
=
max(
dp[i][j],
dp[i-1][j-w]+v
)

完全一致。


第九幕:生活中的分组背包

1、很多同学会问:

这个模型有什么用?


2、其实非常常见!


选课系统

数学组选一门

英语组选一门

科学组选一门


游戏装备

武器选一件

头盔选一件

鞋子选一件


旅游套餐

机票选一种

酒店选一种

景点套票选一种


全部都是:

🌟分组背包


🎯本课总结


1、分组背包定义

物品分组。

每组:

复制代码
最多选一个

2、状态定义

复制代码
dp[i][j]

前 i 组

容量 j

最大价值


3、转移公式

复制代码
dp[i][j]
=
max(
dp[i-1][j],
dp[i-1][j-w]
+
v
)

枚举组内所有物品。


4、循环结构

复制代码
for(组)
{
    for(容量)
    {
        for(组内物品)
        {
        }
    }
}

5、一维优化

容量:

复制代码
倒序

🏹课后挑战

1、背包容量:

复制代码
5

2、第一组(武器)

重量 价值
1 2
3 5

3、第二组(头盔)

重量 价值
2 3
3 4

4、第三组(鞋子)

重量 价值
1 2
2 4

5、请同学们:

① 画出完整的 dp[i][j] 表。

② 求出最终答案。

③ 思考:


6、为什么同一组要使用倒序循环?

恭喜你!已经掌握了 分组背包的核心思想


相关推荐
雪度娃娃1 小时前
转向现代C++——保证const成员函数的线程安全性
开发语言·c++
坚果派·白晓明1 小时前
[鸿蒙PC三方库移植适配] 使用 AtomCode + Skills 自动完成Protobuf鸿蒙化适配
c语言·c++·华为·harmonyos
原来是猿2 小时前
深入理解 C++ unordered_map 与 unordered_set
开发语言·c++
满天星83035772 小时前
【Qt】信号和槽 (一)(概述和基本使用)
开发语言·c++·qt
努力的章鱼bro2 小时前
CUDA编程模型
c++·cuda
8Qi82 小时前
LeetCode 337:打家劫舍 III(House Robber III)—— 题解 ✅
算法·leetcode·二叉树·动态规划
会周易的程序员2 小时前
C++ 对象池深度解析:架构设计与实现原理
开发语言·c++·物联网·iot·aiot
lilili也2 小时前
C++:类
c++
L_09072 小时前
【C++】智能指针
开发语言·c++·智能指针