算法第十五期——动态规划(DP)之各种背包问题

news2024/11/26 2:37:02

目录

0、背包问题分类

1、 0/1背包简化版

【代码】

2、0/ 1背包的方案数

【思路】

【做法】

【代码】

空间优化1:交替滚动

空间优化2:自我滚动

 3、完全背包

【思路】

【代码】

4、分组背包 

核心代码

5、多重背包

多重背包解题思路1:转化为0/1背包

多重背包解题思路2:直接DP

         核心代码

多重背包解题思路3:二进制拆分优化

拆分要点

多重背包解题思路4:单调队列

模板题

【代码】


0、背包问题分类

背包问题可分为0/1背包简化版,背包方案数,完全背包,分组背包,多重背包

1、 0/1背包简化版

0/1背包的简化版:不管物品的价值。把体积看成最优化目标:最大化体积。 

装箱问题        lanqi ao0J题号763

题目描述

有一个箱子容量为 V(正整数,0≤V≤20000),同时有 n 个物品(0≤n≤30),每个物品有一个体积(正整数)。

要求 n 个物品中,任取若干个装入箱内,使箱子的剩余空间为最小

输入描述

输入第一行,一个整数,表示箱子容量。

第二行,一个整数 n,表示有 n 个物品。

接下来 n 行,分别表示这 n 个物品的各自体积。

输出描述

输出一行,表示箱子剩余空间。

输入输出样例

输入

24
6
8
3
12
7
9
7

输出

0

0/1背包的简化版,不管物品的价格。把体积(不是价格)看成最优化目标:最大化体积。

【代码】

dp = [0]*20010
V = int(input())# 容量
n = int(input())# 物品数
c = [0]*40      # 存每个物品体积
# 读入每个物体的体积
for i in range(1, n+1): 
    c[i]=int(input ())
# 自我滚动
for i in range (1, n+1) :
    for j in range (V, c[i]-1,-1):
        dp[j] = max(dp[j],dp[j-c[i]]+c[i])
print(V-dp[V]) # 剩余最小容量 = 容量 - 物品最大体积

2、0/ 1背包的方案数

2022年届国赛,填空题,langiao0J题号2186

问题描述

将 2022 拆分成 10 个互不相同的正整数之和, 总共有多少种拆分方法?

注意交换顺序视为同一种方法, 例如 2022=1000+1022 和 2022=1022+1000 就视为同一种方法。

答案提交

这是一道结果填空的题, 你只需要算出结果后提交即可。本题的结果为一 个整数, 在提交答案时只填写这个整数, 填写多余的内容将无法得分。

【思路】

  • 题目求10个数的组合情况,这十个数相加等于2022。因为是填空题可以不管运行时间,看起来可以用暴力for循环10次,加上剪枝。然而暴力的时间极长,因为答案是:379187662194355221。
  • 建模:这一题其实是0/1背包背包容量为2022,物品体积为1~2022,往背包中装10个物品,要求总体积为2022,问一共有多少种方案。
  • 与标准背包的区别:是求方案总数

 【做法】

  • 定义dp[ ][ ][ ]: dp[i][i][k]表示数字1~i取j个,容量为k的方案数
  • 下面的分析沿用标准0/1背包的分析方法。
  • 从i-1扩展到i,分两种情况:

        (1) k>i:数i可以要,也可以不要。
             要i:   从1~i-1中取j-1个数,再取i,等价于dp[i-1][j-1][k-i]。

             不要i:从1~i-1中取j个数,等价于dp[i-1][i][k]
             合起来(要和不要的总方案数): dp[i][i][k] = dp[i-1][i][k] + dp[i-1][j-1][k-i]
        ( 2) k<i:由于数i比总和k还大,显然i不能用。有: dp[i][i][k]= dp[i-1][ji][k]

【代码】

空间优化1:交替滚动

dp = [[[0]*2222 for i in range(11)] for j in range(2222)]   # 比题目要求的数据范围大一点
for i in range(0,2023): dp[i][0][0]=1 # 初始化:递推条件的初始值,不选也是一种方案
for i in range(1,2023):
    for j in range(1,11):
        for k in range(1,2023):
            if k<i: dp[i][j][k] = dp[i-1][j][k]
            else:dp[i][j][k] = dp[i-1][j][k]+dp[i-1][j-1][k-i]
print(dp[2022][10][2022])

空间优化2:自我滚动

dp = [[0]*2222 for i in range(11)]
dp[0][0] = 1    #特别注意这个初始化
for i in range(1,2023):
    for j in range (10,0,-1):    # 10个数
        for k in range(i,2023):  # k>=i
            dp[j][k] += dp[j-1][k-i]
print(dp[10][2022])

 3、完全背包

  • 特点:每种物品有无穷多个 

小明的背包2lanqiao0J题号1175

题目描述

小明有一个容量为 V 的背包。

这天他去商场购物,商场一共有 N 种物品,第 i 种物品的体积为 wi​,价值为 vi​,每种物品都有无限多个

小明想知道在购买的物品总体积不超过 V 的情况下所能获得的最大价值为多少,请你帮他算算。

输入描述

输入第 1 行包含两个正整数 N,V,表示商场物品的数量和小明的背包容量。

第 2∼N+1 行包含 2 个正整数 w,v,表示物品的体积和价值。

1≤N≤10^3,1≤V≤10^3,1≤wi​,vi​≤10^3。

输出描述

输出一行整数表示小明所能获得的最大价值。

输入输出样例

输入

5 20
1 6
2 5
3 8
5 15
3 3 

输出

120

【思路】

和0/1背包类似。0/1背包的每种物品只有1件,完全背包的每种物品有无穷多件,第i种可以装0件、1件、2件、.....、C//c_i件。
做法:定义dp[i][j]:把前i种物品(从第1种到第i种)装入容量为j的背包中获得的最大价值。把每个dp[i][j]都看成一个背包:背包容量为j,装1~i这些物品。最后得到的dp[N][C]就是问题的答案:把N种物品装进容量C的背包的最大价值。
区别:0/1背包问题中,每个物品只有拿与不拿两种;而完全背包问题,需要考虑拿几个

【代码】

完全背包的代码和0/1背包的代码相似,只多了一个k循环,用来遍历每种物品拿几个。

def solve(n,C) :
    for i in range (1, n+1):
        for j in range (0,C+1):
            dp[i][j] = dp[i - 1][j]      # 初始化为都不装的情况
            for k in range(1,j//c[i]+1): # 可以拿1~j//c[i]个该物品  k*c[i]<=j #在容量为j的背包中放k个
                dp[i][j] = max(dp[i][j],dp[i - 1][j - k * c[i]] +k * w[i])
    return dp[n][C]

N = 3011
dp = [[0]*N for j in range(N)]
w =[0]*N; c = [0]*N
n,C = map(int,input().split())
for i in range(1, n+1):
    c[i], w[i] = map(int,input ().split())  # 每个物品的体积和价值
print(solve(n,C))

也可以不需要初始化条件,但下面要从0个该物品开始遍历,这样写代码更加简洁,但时间复杂度高了一点,代码如下:

def solve(n,C) :
    for i in range (1, n+1):
        for j in range (0,C+1):
            dp[i][j] = dp[i - 1][j]      # 初始化为都不装的情况
            for k in range(1,j//c[i]+1): # 可以拿1~j//c[i]个该物品  k*c[i]<=j #在容量为j的背包中放k个
                dp[i][j] = max(dp[i][j],dp[i - 1][j - k * c[i]] +k * w[i])
    return dp[n][C]

N = 3011
dp = [[0]*N for j in range(N)]
w =[0]*N; c = [0]*N
n,C = map(int,input().split())
for i in range(1, n+1):
    c[i], w[i] = map(int,input ().split())  # 每个物品的体积和价值
print(solve(n,C))

4、分组背包 

 分组背包问题:

  • 有一些物品,把物品分为n组,其中第i组第k个物品体积是c[i][k],价值是w[i][k];
  • 每组内的物品冲突,每组内最多只能选出一个物品;
  • 给定一个容量为C的背包,问如何选物品,使得装进背包的物品的总价值最大。

解题思路与0/1背包相似。

  • 0/1背包dp[i][j]:把前i个物品(从第1个到第i个)装入容量为j的背包中获得的最大价值。
  • 分组背包dp[i][j]:把前i组物品装进容量j的背包(每组最多选一个物品),可获得的最大价值。
  • 状态转移方程:
                             dp[i][j] = max {dp[i-1][j],dp[i-1][j-c[i][k]] + w[i][k]}                                      dp[i-1][j]表示第i组不选物品,dp[i-1][j-c[i][k]]表示第i组选第k个物品
  • 求解方程需要做i、j、k的三重循环。

核心代码:

状态转移方程:dp[i][j] = max {dp[i-1][j], dp[i-1][j-c[i][k]] + w[i][k]},用滚动数组,变为:dp[j] = max {dp[j],dp[j-c[i][k]]+ w[i][k]}

dp = [0] * N
for i in range(1, n + 1):       # 遍历每个组
    for j in range(C, -1, -1):  # 枚举容量
        for k in range(1, C + 1):  # 用k枚举第i组的所有物品
            if j >= c[i][k]:       # 第k个物品能装进容量j的背包
                dp[j] = max(dp[j], dp[j - c[i][k]] + w[i][k])  # 第i组第k个
print(dp[C])

5、多重背包

多重背包问题:

  • 给定n种物品和一个背包,第i种物品的体积是ci,价值为wi,并且有mi个,背包的总容量为C。
  • 如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
  • 与完全背包的区别:完全背包是每种物品都有无限多个,而多重背包是有限个

多重背包解题思路1:转化为0/1背包

  • 转换为0/1背包问题。
  • 把相同的m_i个第i种物品看成独立的m_i个,总共\sum_{i=1}^{n}m_i个物品,。
  • 然后按0/1背包求解,复杂度O(C*\sum_{i=1}^{n}m_i)

多重背包解题思路2:直接DP

  • 定义状态dpli][j]:表示把前i个物品装进容量j的背包,能装进背包的最大价值。
  • 第i个物品分为装或不装两种情况,状态转移方程:

                                dp[i][j] = max {dp[i-1][j],dp[i-1][j-k*c[i]] +k*w[i]}
                               1 ≤k ≤min{m[i], j/c[i]}             # k不能超过 m_i个和最大容量个数的最小值

  • 直接写i、j、k三重循环,复杂度和第一种思路的复杂度一样。
  • 对比完全背包:1≤k ≤ j/c[i]

核心代码: 

        状态转移方程:dp[i][j] = max {dp[i-1][j], dp[i-1][j-k*c[i]]+ k*w[i]},用滚动数组,变为:dp[j] = max{dp[j],dp[j-k*c[i]]+ k*w[i]}。

dp = [0]*N
for i in range(1, n+1):           #枚举物品
    for j in range (C,c[i]-1,-1): #枚举背包容量
    for k in range(1,m[i]+1):     #用k遍历第i组的所有物品
        if(j >= k*c[i]):          #第k个物品能装进容量j的背包
            dp[j] = max(dp[j], dp[j-k*c[i]]+k*w[i])
print(dp[C])

 多重背包解题思路3:二进制拆分优化

  • 一种简单而有效的技巧。
  • 例如第i种物品有m_i= 25个,这25个物品放进背包的组合,有0~25的26种情况。
  • 不过要组合成26种情况,其实并不需要25个物品。
  • 根据二进制的计算原理,一个十进制整数X,可以用1、2、4、8、...这些2的倍数相加得到,例如25=16+8+1,这些2的倍数只有logX个
  • 题目中第i种物品有m_i个,用logm_i个数就能组合出0 ~m_i种情况。总复杂度从O(C*\sum_{i=1}^{n})优化到O(C*\sum_{i=1}^{n}log_2m_i)

拆分要点:

  • 注意拆分的具体实现,不能全部拆成2的倍数,而是先按2的倍数从小到大拆,最后是一个小于等于最大倍数的余数。
  • 保证拆出的数相加在[1, mi]范围内,不会大于mi。
  • 例如mi= 25,把它拆成1、2、4、8、10,最后是余数10,10<16,这5个数能组合成1~25内的所有数字,不会超过25。
  • 错误示例:如果把25拆成1、2、4、8、16,相加的范围就是[1,31]了。

多重背包解题思路4:单调队列

因为这一讲主要是讲dp算法,所以就不在直接过多介绍其他算法,但这个方法优化程度更高,有兴趣的朋友可以看看这篇文章:单调队列优化多重背包(全网最详细解析) 

模板题

【输入描述】第一行是整数n和C,分别表示物品种数和背包的最大容量。接下来 n行,每行三个整数 wi、ci、mi,分别表示第i个物品的价值、体积、数量。
【输出描述】输出一个整数,表示背包不超载的情况下装入物品的最大价值。
【输入样例】

4 20

3 9 3

5 9 1

9 4 2

8 1 3

【输出样例】

47

【代码】

 代码用二进制拆分优化来解答。

N = 100010
w = [0] * N;c = [0] * N;m = [0] * N
xw = [0] * N;xc = [0] * N   # 经过二进制拆分后的新体积和新价值,转换后每个物体只有一个,所以没有新的数量

n, C = map(int, input().split())
for i in range(1, n + 1):
    w[i], c[i], m[i] = map(int, input().split())

# 以下是二进制拆分
xn = 0  # 二进制拆分后的新物品总数量
for i in range(1, n + 1):
    j = 1
    while j <= m[i]:  # 例:直到最后一个数m[i](余数)出现
        m[i] -= j     # 减去已拆分的
        xn += 1
        xc[xn] = j * c[i]  # 新物品的体积
        xw[xn] = j * w[i]
        j <<= 1       # 二进制枚举:1,2,4...
    if m[i] > 0:      # 最后一个是余数
        xn += 1
        xc[xn] = m[i] * c[i]
        xw[xn] = m[i] * w[i]
# 以下是滚动数组版本的0/1背包
dp = [0] * N
for i in range(1, xn + 1):  # 枚举物品
    for j in range(C, xc[i] - 1, -1):  # 枚举背包容量  xc[i] - 1这里的-1是因为range函数是左闭右开区间,-1才能取到xc[i]
        dp[j] = max(dp[j], dp[j - xc[i]] + xw[i])
print(dp[C])

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/336497.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL8 创建用户,设置修改密码,授权

MySQL8 创建用户,设置修改密码,授权 MySQL5.7可以 (创建用户,设置密码,授权) 一步到位 &#x1f447; GRANT ALL PRIVILEGES ON *.* TO 用户名% IDENTIFIED BY 密码 WITH GRANT OPTION&#x1f446;这样的语句在MySQL8.0中行不通, 必须 创设和授权 分步执行&#x1f447; CR…

如何动态生成列字段?请看向这里哟

&#x1f343; 场景前言 &#x1f420;一般而言&#xff0c;某个简单查询接口涉及到得表结构不超过三个。如果不是单表操作的话&#xff0c;多个表中间用到联合查询的SQL也可以解决相关问题。但是&#xff0c;事与愿违的是我们的业务是跟着场景走的&#xff0c;并不是所有的业务…

python+django宠物销售商城网站vue

宠物销售商城,在宠物销售商城可以查看首页、商品信息、商品资讯、个人中心、后台管理、购物车、在线客服等内容 用户登录、用户注册,通过注册获取用户名、密码、姓名、联系电话等信息进行注册、登录 商品信息,在商品信息页面可以查看商品名称、商品分类、图片、品牌、规格、价…

java易错题锦集二

源码 补码 int i 5; int j 10; System.out.println(i ~j);有个公式&#xff0c;-n~n1 另一种解题思路 ~代表对n按位取反 10的源码是: 00000000 00000000 00000000 1010 所以对10按位取反就是 11111111 11111111 11111111 0101 由于计算机中-1表示为 11111111 11111111 111…

[docker]笔记-镜像 管理

1、镜像管理 docker search xxxx ①查找镜像,例如查找httpd [rootlocalhost ~]# docker search httpd ②下载镜像 docker pull xxxx [rootlocalhost ~]# docker pull httpd ③列出本地镜像 docker images [rootlocalhost ~]# docker images ④删除镜像 docker rmi xxx…

Linux(十)线程安全 上

目录 一、概念 二、互斥锁实现互斥 三、条件变量实现同步 银行家算法 生产者与消费者模型 一、概念 概念&#xff1a;在多线程程序中&#xff0c;如果涉及到了对共享资源的操作&#xff0c;则有可能会导致数据二义性&#xff0c;而线程安全就指的是&#xff0c;就算对共享…

【MFC】菜单与状态栏(15)

菜单 一般菜单的使用步骤&#xff1a; 1.编辑菜单资源&#xff0c;设置菜单属性&#xff08;包括菜单名和ID&#xff09;&#xff1b; 2.用ClassWizard自动映射菜单消息和成员函数&#xff1b; 3.手工编辑成员函数&#xff0c;加入菜单消息处理代码。 单文档窗口可以设置默…

2年时间,涨薪20k,想拿高薪还真不能老老实实的工作...

2016年开始了我的测试生活。 2016年刚到公司的时候&#xff0c;我做的是测试工程师。做测试工程师是我对自己的职业规划。说实话&#xff0c;我能得到这份工作真的很高兴。 来公司的第一个星期&#xff0c;因为有一个项目缺人&#xff0c;所以部门经理提前结束了我的考核期&a…

C语言预处理

文章目录 目录 文章目录 前言 一、程序编译的过程 二、编译阶段 1.预处理(*.i&#xff09; 2.编译(*.s) 3.汇编(*.o) 4.链接 总结 前言 提示&#xff1a;使用vs code(gcc编译器)与vs2022来演示c语言的预处理 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面…

KEIL编译报错,解决方法汇总

目录 背景 最近在跟着野火码uCosiii的代码时&#xff0c;感觉非常完美&#xff0c;结果一编译&#xff0c;报了120个莫名其妙的问题&#xff0c;下面是踩过的坑&#xff0c;一起记录下&#xff0c;免得下次又掉进去了~ 1. 编译汇编文件&#xff0c;报错 error: unexpected t…

看海泰方圆类ChatGPT技术模型!

ChatGPT&#xff0c;上线2个月便以破亿的用户群引爆了全网。 ChatGPT是由OpenAI公司开发的AI聊天机器人程序&#xff0c;于2022年11月底推出&#xff0c;能够通过学习和理解人类的语言来进行对话、互动&#xff0c;甚至能完成撰写邮件、视频脚本、文案、翻译、代码等任务&…

南卡和JBL无线蓝牙耳机哪款更值得买?横向评测后秒懂差距!

蓝牙耳机想必大家都很熟悉&#xff0c;无论是商务办公还是休闲娱乐&#xff0c;它都起到了至关重要的作用。但蓝牙耳机发展速度太快&#xff0c;耳机品质也参差不齐&#xff0c;最近看到大多数人都有一个疑问&#xff1a;买什么蓝牙耳机比较好&#xff1f;作为一个资深的耳机爱…

动态规划:状态压缩DP(2)(P1896 互不侵犯 ,矩阵计数)

文章目录互不侵犯矩阵计数互不侵犯 题目传送门 在NN的棋盘里面放K个国王&#xff0c;使他们互不攻击&#xff0c;共有多少种摆放方案。国王能攻击到它上下左右&#xff0c;以及左上左下右上右下八个方向上附近的各一个格子&#xff0c;共8个格子。 升级版的八皇后问题&#xf…

(一)随处可见的LED广告屏是怎么工作的呢?

笔者前段时间项目上正好用到一块32*128分辨率的单色LED&#xff0c;正式介入开发的时候&#xff0c;才发现网上的资料少之又少&#xff0c;供应商也给不出有价值的参考信息。故打算分成三篇详细的介绍下开发过程中遇到的问题。本篇主要介绍LED屏幕的一些基础信息&#xff0c;第…

C#互联网医院源码 智慧医院小程序源码 在线问诊在线开方源码

互联网医院平台源码 智慧医院管理系统源码 开发环境&#xff1a;ASP.NET C# VS2019 SQL2008 依托于实体医院利用互联网技术对接院内业务信息系统&#xff0c;向患者提供基于线上问诊、预约挂号、缴费结算、医患互动、诊后随访、健康科普和复诊等全面的医疗健康互联网服务。…

关于apifox和postman接口工具我有话要说~~

Apifox 和 Postman 都是流行的接口测试工具&#xff0c;各自有其优势和缺点。Apifox 的优势在于它提供了强大的可视化界面&#xff0c;可以方便地测试和监控 API。它还支持多种请求方式&#xff0c;并且支持对请求和响应进行代码生成。但是&#xff0c;Apifox 的缺点在于它不太…

详解指针(1)(初阶版)

前言&#xff1a;本篇是详解指针&#xff08;1&#xff09;&#xff0c;内容包括&#xff1a;指针是什么&#xff0c;指针和指针类型&#xff0c;野指针 part 1&#xff1a;指针是什么 1 指针就是地址&#xff0c;口语中说的指针通常指的是指针变量 2 指针变量&#xff1a;存…

shiro721——CVE-2019-12422

这两个漏洞主要区别在于Shiro550使⽤已知密钥碰撞&#xff0c;后者Shiro721是使⽤ 登录后rememberMe {value}去爆破正确的key值 进⽽反序列化&#xff0c;对⽐Shiro550条件只要有 ⾜够密钥库 &#xff08;条件⽐较低&#xff09;、Shiro721需要登录&#xff08;要求⽐较⾼鸡肋 …

Unity基于GraphView的行为树编辑器

这里写自定义目录标题概述基于GitHub上&#xff1a;目前这只是做了一些比较基础的功能节点开发&#xff0c;仅仅用于学习交流&#xff0c;非完成品。项目GitHub连接&#xff1a;[https://github.com/HengyuanLee/BehaviorTreeExamples](https://github.com/HengyuanLee/Behavio…

GNSS 精密钟差产品介绍与DCB改正详解

文章目录前言参考前言 IGS 提供的 GNSS 轨道产品和钟差产品的解算基准并非完全一样, 对于精密产品&#xff0c;各 GNSS 系统的参考基准均为双频无电离层组合&#xff1b;对于广播星历&#xff0c;则区分 GPS 类卫星(GPS,Galileo,QZSS) 基于双频无电离组合的伪距以及 BDS 卫星系…