多重背包的单调队列优化
创始人
2025-05-30 03:13:12
0次

邮专学子周末早起迎寒风细雨所作............. 周末也别想休息!!!

耐心看完,必定有收获哦🥰🐯

多重背包与其他背包相比的最大区别点就是,每个物品的数量是有限的。下面我们来看看多重背包的更新特性,可以从递推公式上琢磨琢磨.......

相关参数的解释

v代表体积,w代表价值,s代表物品数量,i代表放入背包的第i件物品,g数组存放没有放入i物品前各容量背包的原始价值,其它参数根据情况而定。

单调队列

元素值不递增的队列,谓之单调队列,还没弄懂概念的同学可以去搜索相关博客进行理解

背包更新的特性

测试代码

#include
#include
using namespace std;int dp[1005];
int main() {int N, V;cin >> N >> V;for (int i = 1;i <= N;i++) {//从背后开始遍历,避免物品重放int v, w, s;cin >> v >> w >> s;for (int j = V;j >= v;j--) {for (int k = 1;k <= s;k++) {if (j >= k * v) {dp[j] = max(dp[j], dp[j - k * v] + k * w);printf("dp[%d] = %d dp[%d] = dp[%d] + %d*%d\n", j, dp[j], j, j - k * v, k, w);}else {break;}}}}cout << dp[V] << endl;return 0;
}
递推公式:dp[j] = max(dp[j], dp[j - k * v] + k * w)

很显然,背包dp[j]的最大值是由其自身得到,或者是从容量与其相差k*v的背包更新而来。姑且认定容量之间相差k*v的背包为一类,那么对于放入体积为v的物品,背包更新的种类就可以分为起始容量为0,1,2......v-1种,比如dp[0],dp[0+v],dp[0+2v]......dp[0+k*v]......

从此处开始,参数的含义与测试代码不同。

j表示同类背包的起始容量,k表示待更新最大值的背包容量,t表示物品相差个数。

很显然,背包只能在同类间更新,且被更新背包的容量较大,倘若能够一次性选取到dp[k-t*v]+t*w最大的背包maxBag(定义名词),该背包的容量为k-t*v,那么背包dp[k]的最大值只需要更新一次,时间复杂度为O(1)。为了简化问题的讨论,我们姑且认为dp[k]就是从同类背包dp[k-t*v]的基础上更新而来,下面讨论如何遍历特点与选取价值最大的容量背包maxBag。

遍历特点

由于背包在同类间更新,而且较大容量背包的最大价值是由较小背包的最大价值推导而来,因此遍历背包的顺序是从小容量到大容量。然而,这种更新方式会使物品重复放入,因而需要用一个辅助数组g来存放在没有放入第i件物品之前,各个容量背包的最大价值。那么递推公式应该为:

dp[k] = max(g[k],g[k-t*v]+t*v)

如何选取maxBag

(j代表同类背包的起始容量,牢记参数含义)

给出存放规则如下:倘如g[k2] - (k2 - j)/v*w >=g[k1] - (k1 - j)/v*w ,那么

由于i物品的数量s有限,故dp[k]背包所借助的maxBag,其容量vv,必须满足如下关系式: ;因此,由于maxBag的使用受到物品个数影响,故当maxBag1不能再被使用时,需要借助maxBag2,也就是说,我们需要借助一个队列来存放此类背包{maxBag1,maxBag2......maxBag(n)},倘若待更新背包的容量k满足:,那么maxBag1更新最大容量,否则maxBag1退出队列,借助maxBag2更新dp[k]的最大价值。

因而,我们需要借助 int q[maxn]来存放maxBag,队头的下标为hh,队尾的下标为tt,q[x]代表maxBag的背包容量,下面给出maxBag的入队规则,假如 g[k1] - (k1-j)/v*w >= g[k2] - (k2 - j)/v*w,那么容量为k1的背包必定排在容量为k2的背包前面,这是因为当我们分别讨论dp[k]借助g[k1],g[k2]更新最大价值时(依旧先前规定,不讨论dp[k]借助自身更新最大值):

dp[k] = g[k1] + (k-k1)/v*w >= dp[k] = g[k2] + (k - k2)/v*w,该不等式很容易证明。

总而言之,倘若q[hh]代表队头背包的容量,因而 当(k-q[hh])>s*v时,hh++,当dp[k]结束更新后需要考虑将其加入队列,dp[k]的更新公式如下:

dp[k] = max(g[k], g[q[hh]] + (k - q[hh]) / v * w)

代码实现

语言:C++ 环境:VS2022

(可以用于通过AcWing困难级别的多重背包问题)

#include
#include
using namespace std;const int maxv = 20005;
const int maxn = 1005;
int dp[maxv];
int g[maxv];int main() {int N, V;cin >> N >> V;//确保容量的最大值不超过maxvif (V >= maxv) {return 0;}//依次放入N件物品for (int i = 1;i <= N;i++) {int v, s, w;cin >> v >> w >> s;//将v分类,g表示在放入i物品之前的各容量背包的价值状态memcpy(g, dp, sizeof dp);for (int j = 0;j < v;j++) {//讨论容量为k的背包的更新,hh表示队头,tt表示队尾//q[hh]存放最大净值背包的容量    int hh = 0, tt = -1;int q[maxv] = { 0 };for (int k = j;k <= V;k += v) {//如果队列的长度大于物品的个数,那么需要更新队头if (hh <= tt && (k - q[hh]) > s * v)  hh++;//更新最大容量if (hh <= tt)  dp[k] = max(g[k], g[q[hh]] + (k - q[hh]) / v * w);//将dp[k]while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w)tt--;q[++tt] = k;}}}cout << dp[V] << endl;return 0;
}

算法的时间复杂度

由于每当加入一个物品时,各个容量的背包只需要更新一次,因此倘若不包括队列的更新,则时间复杂度为O(NV),N代表物品个数,V代表背包最大容量。再讨论最内层的队列更新循环,由于队列最大长度不超过物品的个数s,故更新最好的情况为O(1),最坏情况为O(s),故总体时间复杂度的范围是:O(NV)~O(NVs),由于s是一个变量,而且相对于NV而言比较小,姑且认为时间复杂度为O(NV).相比如朴素算法的O()已经提升一大截了。

结语

队列优化多重背包算法涉及将背包本身按照容量分类的思想(二进制优化是将物品个数分类),需要理解其分类思想,背包更新的特性以及如何利用单调队列更新最大价值.......

思路有点复杂,一遍看不懂不要灰心,多去看看别人的博客,多看看相关视频,多敲几遍代码自己体会,很快就会有头绪了(本人也是花了一整天时间才了解了一点点)。最后希望同学们若真的理解了,将自己的理解分享出去,这样会进一步加深对该算法的理解呢。

结束!!!

相关内容

热门资讯

总算了解开挂!众乐乐开挂AI攻... 总算了解开挂!众乐乐开挂AI攻略,众乐乐多功能都是存在有开挂攻略1、上手简单,内置详细流程视频教学,...
程序员教你辅助!英豪娱乐城辅助... 程序员教你辅助!英豪娱乐城辅助AI工具,英豪娱乐城多功能原来真的是有辅助工具暗藏猫腻,小编详细说明英...
盘点几款辅助!冰球突破辅助AI... 盘点几款辅助!冰球突破辅助AI插件,冰球突破多功能其实真的有辅助插件1、冰球突破脚本辅助下载、冰球突...
分享一款辅助器!建湖好运麻将辅... 分享一款辅助器!建湖好运麻将辅助器AI教程,建湖好运麻将多功能竟然真的是有辅助器教程1、在建湖好运麻...
每日必备透视挂!新老夫子透视挂... 每日必备透视挂!新老夫子透视挂AI技巧,新老夫子多功能原来真的有透视挂技巧1、新老夫子脚本辅助下载、...
分享认知作弊器!星际互联作弊器... 分享认知作弊器!星际互联作弊器AI方法,星际互联多功能都是存在有作弊器方法1、每一步都需要思考,不同...
玩家必备攻略作弊器!闲逸斗地主... 玩家必备攻略作弊器!闲逸斗地主作弊器AI方法,闲逸斗地主多功能果然是有作弊器方法1、闲逸斗地主透视辅...
分辨真假透视挂!宁波游戏大厅透... 分辨真假透视挂!宁波游戏大厅透视挂AI软件,宁波游戏大厅多功能一直是有透视挂软件1、让任何用户在无需...
每日必看教程透视挂!君悦来透视... 每日必看教程透视挂!君悦来透视挂AI工具,君悦来多功能本来是有透视挂工具1、上手简单,内置详细流程视...
发现一款外挂!半岛外挂AI工具... 发现一款外挂!半岛外挂AI工具,半岛多功能一贯真的有外挂工具1、起透看视 半岛辅助软件价格2、随意选...