背包DP笔记
背包问题
01 背包问题
有
思路:每种物品仅有一件,可以选择放或不放。令
显然,
该算法的时间复杂度为
覆盖式填写
考虑将空间优化至
void 01pack(int w,int v){ for(int i=V;i>=w;i) f[i]=max(f[i],f[i-w]+v; return ; }
下文中,如果出现处理一件 01 背包中的物品,将会直接调用此函数而不加说明。
完全背包问题
有
这个问题与“01背包问题”很类似,不同的是每种物品能取无数件,令
这样的做法时间复杂度超过了
贪心优化
若两件物品
对于随机生成的数据,这个方法可以大大加快速度,但可能被特别设计的数据卡掉。
还有一种做法:首先将体积大于
该优化会在之后多次提到。
转换为01背包求解
最简单的想法是,把第
第二种想法是使用二进制拆分,把第
的算法
首先,为什么01背包的覆盖式填写要从右往左?这是因为要保证
void INFpack(int w,int v){ for(int i=w;i<=V;i++) f[i]=max(f[i],f[i-w]+v; return ; }
多重背包问题
有
这个问题只要将完全背包的方程略加改动即可,令
时间复杂度是
转化为01背包问题
仍然考虑二进制拆分的思路,将第
void multipack(int w,int v,int t){ int k=1; while(k<t){ 01pack(k*w,k*v); t-=k,k<<=1; } if(t) 01pack(t*w,t*v); }
时间复杂度是
算法
多重背包同样具有
对于该状态转移方程:
考虑状态
把倒序循环
将
这样将右侧的式子分为
把
整个算法的时间复杂度为
混合背包问题
有
该问题看似困难,但只要分别调用以上三种背包即可解决,思路也很直观:
for(int i=1;i<=n;i++) 01pack(w[i],v[i]); for(int i=n+1;i<=n+m;i++) multipack(w[i],v[i],t[i-n]); for(int i=n+m+1;i<=n+m+k;i++) INFpack(w[i],v[i]);
此处的时间复杂度取决于 multipack
函数的复杂度,如果使用单调队列则是
二维费用背包问题
二维费用的背包问题是:对于每件物品,有
对于这种题目,只需在原来的基础上将状态也加
二维费用的背包也可使用覆盖式填写,因此时间复杂度为
物品个数的限制
有时,背包要求最多取
当发现由熟悉的动态规划题目变形得来的题目时,在原来的状态中加一纬以满足新的限制是一种比较通用的方法。
分组背包问题
有
令
由于我们不清楚每组物品有几个,因此可以使用 vector
存储。
注意:三层循环的顺序为 “所有的组
for(int i=1;i<=cnt;i++){ for(int j=V;j>=0;j){ for(int k=0;k<id[i].size();k++){ if(j>=w[id[i][k]]) f[j]=max(f[j],f[j-w[id[i][k]]]+v[id[i][k]]; } } }
另外,可以对每组内的物品应用贪心优化,详情见“完全背包问题”。
依赖背包问题
这种背包的物品之间存在某种依赖关系,也就是说,
化简的问题
我们假设,没有物品既依赖与别的物品,又被别的物品依赖,同时,没有物品依赖多个物品
这个问题由 NOIP2006金明的预算 一题扩展而来,我们称不依赖于别的物品的物件成为 “主件”,依赖于别的物品的物件成为 “附件”,可知所有的物品组由一个 “主件” 和若干个 “附件” 集合而成。
最直接的思路是:依次考虑每一个物品集,但可用的策略极多,包括:一个物品也不选、选择主件、选择主件和
考虑到所有的策略都互斥,所以一个主件和它的附件集合相当于 “分组背包问题” 里的一个物品组,只能从中选择一个策略,它的价值和话费都是这个策略中的物品和,但每个物品组的物品数仍旧很多,和原思路的策略数相同(原因显然)。
再考虑使用 “完全背包问题” 里提到过的贪心优化,对于一个物品组内,费用相同的物品只留下价值最大的那一个,不影响结果。所以,我们可以对每个主件的附件集合进行01背包,得到费用
一般的问题
一般的问题是:依赖关系以图论中的 “森林” 形式给出(森林即多叉树的集合),也就是说,主件的附件依然可以具有自己的附件集合,限制是只有一个主件,且不会出现环。
解决这个问题任然可以用将每个主件及其附件集合都转化为物品组,但不同的是,若这个附件也有附件集合,则它要先转化为物品组,然后用分组背包解出主件与附件集合所对应的附件组中各个费用的附件所对应的价值。
事实上,这是树形DP,其特点是每个父结点都需要对它各个儿子的属性进行DP已求得自己的相关属性。
泛化物品背包问题
考虑这样一种物品,它没有固定的费用和价值,它的价值随着你分配的费用的变化而变化,这就是 “泛化物品”。
更严格的定义,在容量为
一个费用为
一个物品组可以看做一个泛化物品,对于
泛化物品的和
对于两个泛化物品
由此定义泛化物品的和:
泛化物品的定义说明:在背包问题中,若将
背包问题的泛化物品
一个背包问题肯定能对应于某个泛化物品,也就是说,给定所有条件后,我们就可以对每个非负数
综上所述,一般而言,求解背包问题即求解这个问题所对应的函数,也就是该问题所对应的泛化物品,而求解泛化物品的方法就是把它表示成若干个泛化物品的和然后求之。
背包问题的特殊问法
背包问题的问法非常灵活,例如,求解最多可以放置多少件物品或者最多可以装满多少背包空间,都可以根据具体问题利用前面的方程求解出所有状态的值(即
输出方案
一般而言,背包问题是要求一个最优值,如果要求输出这个最优值的方案,可以参照一般动态规划输出方案的方法:记录下每个状态的最优值是由哪个状态转移得来的,换句话说,记录它是由哪个策略推出来的。便可根据这个状态继续往上寻找,直到寻找到最初状态。
以01背包为例,如果要求输出“选择了哪几个物品”,可以再加一个数组
或者,可以根据
输出字典序最小的最优方案
这里字典序最小的意思是
一般而言,求字典序最小的方案只需要在转移时注意策略,首先,如果存在一个选择
但也许更加简单的方法是将物品逆序排列一下。可以按照经典方法的状态转移方程来求值,只是输出方案时,如果
输出方案总数
对于给定
这类问题一般只需将状态转移方程中的
初始条件为
这种做法的可行性在于状态转移方程已经考察了所有可能的背包组成方案。
输出最优方案的总数
这里的方案总数指的是物品总价值最大的方案数,以01背包为例:
令
for(int i=1;i<=n;i++){ for(int j=0;j<=V;j++){ f[i][j]=f[i-1][j]; if(j>=w[i]) f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i]); if(f[i][j]==f[i-1][j]) g[i][j]+=g[i-1][j]; if(f[i][j]==f[i-1][j-w[i]]+v[i]) g[i][j]+=g[i-1][j-w[i]]; } }
求第 优解
对于求第
其基本思想是将每个状态表示成有序队列,将状态转移方程中的
经典01背包求解状态转移方程为:
如果要求解第
因此原方程可以理解为:
一个正确的状态转移方程的求解过程遍历了所有的可用策略,也就覆盖了问题的所有方案,只不过是因为求解最优解,所以其它在任何一个策略上达不到最优的方案都被忽略了。如果把每个状态表示成一个大小为
另外还要注意题目对 “第
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix