欢迎来爆踩我~|

AFewSuns

园龄:4年11个月粉丝:42关注:3

背包类问题总结

零、一些记号与约定

物品种类个数:n

背包最大容量:m,无特殊声明外非负。

每种物品的体积:vi,无特殊声明外非负。

每种物品的价值:wi,无特殊声明外非负。

每种物品的数量:ci,无特殊声明外 ci=1

物品体积的最大值:V=maxivi

物品价值的最大值:W=maxiwi

物品数量的最大值:C=maxici

一、朴素动态规划

01 背包

n 个物品,每个物品有体积 vi 和价值 wi,求体积和不超过 m 的情况下价值和的最大值。

fi,j 表示当前考虑了前 i 个物品,体积和为 j 的最大价值和。

转移即为 fi1,j+wifi,j+vi

考虑把第一维去掉,设 fj 表示目前体积和为 j 的最大价值和。

每次新加第 i 个物品,有转移 fj+wifj+vi。实际上不需要 f,因为 vi0,所以可以直接倒序枚举 j 转移来避免后效性。

时间复杂度 O(nm),空间复杂度 O(n+m)

for(int i=1;i<=n;i++) for(int j=m-v[i];j>=0;j--) f[j+v[i]]=max(f[j+v[i]],f[j]+w[i]);

完全背包

n 个物品,每个物品有体积 vi 和价值 wi,数量有无限个(ci=+),求体积和不超过 m 的情况下价值和的最大值。

01 背包差不多,只不过转移变为 fj+kwifj+kvi(k1),直接顺序 dp 即可。

时间复杂度 O(nm),空间复杂度 O(n+m)

for(int i=1;i<=n;i++) for(int j=0;j<=m-v[i];j--) f[j+v[i]]=max(f[j+v[i]],f[j]+w[i]);

多重背包

n 个物品,每个物品有体积 vi 和价值 wi,数量为 ci,求体积和不超过 m 的情况下价值和的最大值。

还是考虑 01 背包的 dp 形式,转移变为 fj+kwifj+kvi(k[1,ci])

二进制分组优化背包

将每个 ci 拆成 20+21++2k+(m2k+1+1),m2k+1+1[0,2k+1),容易证明 [0,ci] 的所有数都可以被这 logci+1 个数表示出来。

注意不是将 ci 直接二进制拆分!!!

那么这就变成了 nlogC 个物品的 01 背包问题。举个例子,第 i 个物品的 ci 被分成的其中一个数为 x,那么这个新物品的体积为 xvi,价值为 xwi,数量为 1

时间复杂度 O(nmlogC),空间复杂度 O(nlogC+m)

单调队列优化背包

每次新加第 i 个物品,将 fj 按照 jmodvi 分组,那么显然只有同一组的会互相转移。

假设 j1,j2 属于同一组且 j1<j2,那么转移可以改写为 fj1+(j2vij1vi)wifj2,且要求 j2vij1vici

直接用单调队列优化,时间复杂度 O(nm),空间复杂度 O(n+m)

二、特殊背包问题解法

贪心+小范围 dp

适用范围:任意背包,m,vi,wi 可负。

首先对物品进行一下预处理:若 vi0,wi0,则此物品一定不优,丢掉;若 vi0,wi0,则先默认选取此物品,然后将 vi,wi 取反。

然后按性价比排序,贪心从前往后选取物品直至下一步会超过 m 或者取完了。如果取完了则直接输出答案,否则此时选取物品的体积总和 S(mV,m]

考虑目前贪心的方案与最终答案的区别。首先最终答案的体积总和 S 一定也属于 (mV,m](否则可以多选物品),然后最终方案一定是在目前方案的基础上,丢掉一些物品,再选择一些物品得到的。

由于所有物品 |vi|V,于是可以通过调整操作顺序使得体积总和 S 始终处于 (mV,m+V]。具体来说,若当前 Sm,则选择加入物品的操作;否则选择丢掉物品的操作。

进一步观察可以发现,在上述操作的过程中,一定不会出现多次相同的 S。证明大概就是考虑第一次到达 S 和第二次到达 S 中间的操作,由于第二次更优,所以中间操作的 Δw>0。将这些操作作用在一开始贪心得到的方案,就得到了一个体积和与一开始相同,且价值和更大的方案。然而我们一开始是按照性价比贪心选取的,选出来的一定是当前体积和的最优解(类似实数背包),矛盾。

于是之后最多进行 2V 次操作。枚举 n 种物品,假设第 i 种物品在贪心的时候选了 di 个,还剩 cidi 个,那么第 i 种物品就可以进行 di 次丢掉物品的操作和 cidi 次加入物品的操作,根据 vi 的正负性放进背包。由于最多选择 2V 个物品,所以背包容量大小是 2V2 级别的。

同理,还可以做恰好体积和为 m 的背包。时间复杂度 O(nlogn+nV2),空间复杂度 O(n+V2)

例题:

[BalticOI 2022 Day1] Uplifting Excursion

分治法

适用范围:完全背包

f(S)S 内所有物品的体积之和。考虑某个选取方案 X,那么一定存在某种将 X 划分为 SX/S 的方式,使得 |f(S)f(X/S)|V

证明:考虑 [f(X)V2,f(X)+V2] 这段长为 V 的区间,将 X 中的数一个个加进 S,由于 f(S) 的跨度不超过 V,所以一定存在某个时刻使得 f(S) 在这段区间里面。

所以如果要求体积和恰好为 m 的完全背包问题,就可以将其划分为两个完全独立的子问题,即递归求出 [mV2,m+V2] 内的答案后,更新 m 的答案。

由于 [l,r] 的区间会递归到 [lV2,r+V2],初始区间为 [m,m],所以区间长度始终不会超过 2V,递归 logm 层之后区间内的值就会变得很小,可以直接预处理。

如果不要求恰好为 m,由于最终答案的体积和一定属于 (mV,m],所以就令初始区间为这个,区间长度仍然不会超过 2V

具体实现可以不用递归。考虑第 i 层的区间大概就是 [m2iV,m2i+V],预处理出大概 [0,2V] 的 dp 值后直接按层从下往上推就行了。

时间复杂度 O(nV+V2logm)

例题:

gym10106L The Knapsack problem

子集和问题

适用范围:01 背包,没有 wiwi 全相同

折半搜索

分别枚举前一半和后一半的所有情况,之后用哈希表求解,时间复杂度 O(2n2)

如果是求有背包容量上限的最大体积和,那么可以排序之后双指针,时间复杂度 O(n2n2),空间复杂度 O(n+2n2)

bitset 优化

朴素算法

直接套用 01 背包。由于没有 wi,所以 f 的值只有 0/1,表示是否可行,用 bitset 优化转移即可做到时间复杂度 O(nmω),空间复杂度 O(n+mω)

随机算法

首先贪心地从前往后选物品,使得总体积 Sm

然后考虑当前与最优方案的区别,一定是在目前方案的基础上,丢掉左边的物品,再选取一些右边的物品。

将左边的 vi 取反,那么问题变为一个新的 01 背包问题,要选一些数使得和恰好为 mS

假设我已经知道了要选取哪些数使得这些数的和为 mS,那么将它随机打乱,其前缀和的绝对值最大值是 nV 级别的。

于是将 vi 随机打乱,做容量为 nV 级别的 bitset 优化 01 背包,时间复杂度 O(nnVω),空间复杂度 O(n+nVω)

二进制分组

S=vi,那么此算法可以同时对所有 m[1,S] 求解。

vi 相同的物品合并到一起,然后用二进制分组优化。

考虑优化完后还剩多少个物品,设 ai 为初始体积为 i 的物品数量,那么最后会剩 k0i=1S[ai2k] 个物品。

由于 i=1Siai=S,所以 ai2k 的数量最多 S2k 种,总和即为 k0S2k=O(S) 种。

时间复杂度 O(n+SSω),空间复杂度 O(n+Sω)

更优秀的线性解法

整体思路其实跟之前的「贪心+小范围 dp」差不多。首先贪心地选取前面的物品使得总和 m,若取完了则直接输出答案,注意可以不用对 vi 排序。

之后还是考虑与最优方案的区别。设贪心时已经取完了前 pos 个数,后面 npos 个数没有选,那么最优方案一定是在目前方案的基础上,丢掉一些左边的物品,再选取一些右边的物品得到的。

不妨假设是从中间的断点 pos 往两边操作的,若当前体积和 m,则选择右边的物品加入,否则选取左边的物品丢掉,这样体积和始终在 (mV,m+V] 内。

于是设计 dp 状态 fl,r,w 表示只考虑操作 [l,r] 内的数,是否可以构造出和为 w 的方案。转移即枚举上一步操作是从 fl+1,r 还是 fl,r1 转移过来,以及 lr 有没有操作。具体转移如下:

  • fl,r,wfl,r1,w

  • fl,r,wfl+1,r,w

  • fl,r,w+arfl,r1,w(wm)

  • fl,r,walfl+1,r,w(w>m)

需要注意到 w(mV,m+V],即 w 范围是 O(V) 的。

考虑优化,设 gr,w 表示要使得 fl,r,w=1l 最大是多少,没有则记为 0

转移时还是考虑上一步从哪边转移,以及 l,r 要不要操作。具体转移如下:

  • gr,wgr1,w,即从 r1 转移过来且不操作 r

  • gr,w+argr1,w(wC),即从 r1 转移过来且操作 r

  • gr,wall(w>C,l<gr,w),即从左边转移过来且操作 l

从左边转移但不操作 l 的情况不需要考虑,因为 g 取的是 max

状态数变少了,但复杂度没变,其瓶颈在于第 3 步。注意到当 l<gr1,w 时,其会在 r1 时就被转移,然后从第 1 步转移回来。所以只需要转移 lgr1,w 即可。对于单个 w,转移的复杂度为 O(gr,wgr1,w)=O(n),于是总时间复杂度为 O(nV)。空间复杂度用滚动数组优化可以做到 O(n+V)

例题:

Subset Sum

[ABC221G] Jumping sequence题解

决策单调性优化背包

适用范围:任意背包

当所有的 vi 相同时,可以直接将 wi 从大到小排序后贪心选,这些点构成了一个凸包。这启发我们在 V 很小(或者 vi 种类数很少)的时候尝试把这些凸包加起来。

直接做闵可夫斯基和是错的,因为这样每次只能保留 dp 数组构成的凸包上的点,而非凸包位置上的答案是无法被计算的。凸包上的点在后续过程中并不能完全偏序非凸包上的点,于是这是错误的。

假设现在正在把体积为 v 的凸包加上去,于是按照 imodv 的值对当前背包 dp 数组 fi 分组,每组单独做。

每一组都是形如一个 ci=maxj=0iaj+bij(max,+) 卷积形式,不妨设 fi 表示的是体积和 i 的答案,那么序列 a 会满足单调性,序列 b 会满足凸性(也就有四边形不等式)。

设矩阵 Ai,j=aj+bij(ji),那么不难证明 A 满足四边形不等式,即 A 为完全单调矩阵,使用 SMAWK 求解每行的最大值所在位置即可。

时间复杂度 O(nlogn+mV),空间复杂度 O(n+m)。如果偷懒用分治或二分栈,后面的复杂度会带一个 log,但是常数较小。

什么,不会 SMAWK?来我的博客看看吧~

多项式优化背包

适用范围:无限背包,算方案数而非最优化,可对 [1,m] 都求答案

多项式不在本文考虑范围内,只是浅浅地点一下。

考虑写出体积为 v 的生成函数:Fv(x)=11xv,于是答案即为 [x1m]i=1nFvi(x)

直接卷积不太行,考虑把所有 ln 加起来再 exp 回去。

注意到 ln(1xv)=i1xvii,于是只需要统计出每个 v 的出现次数,然后给对应的 mv 项系数加一下就行了。

时间复杂度 O(n+mlogm),空间复杂度 O(n+m)

例题:

付公主的背包

本文作者:AFewSuns

本文链接:https://www.cnblogs.com/AFewSuns/p/knapsack.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   AFewSuns  阅读(1001)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起