细谈多重背包问题

简介: 细谈多重背包问题

多重背包问题朴素解法

多重背包问题是背包问题的一种扩展,与0/1背包问题和分数背包问题有些不同。在多重背包问题中,每种物品都有限定的数量,不再是仅有一个,而是有多个。问题的描述如下:

给定一个背包容量为C,有n种物品,每种物品有重量w[i]、价值v[i]和数量s[i]。目标是选择物品放入背包,使得它们的总重量不超过背包容量,并且总价值最大。

这个问题在一些应用中更为真实,例如购物时某种商品有多件可供选择。解决多重背包问题的方法通常是在0/1背包问题的基础上进行一些调整。

经典解法就是三重for循环,一层枚举物品个数,二层枚举背包容量(逆序),三层枚举物品个数。

#include<stdio.h>
int dp[6001],w[501],v[501],nums[501];//dp[i]表示背包容量为i时所获得最大价值
int n,m;
int max(int x,int y){
  return x>y?x:y;
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++){
    scanf("%d%d%d",&v[i],&w[i],&nums[i]);
  }
  for(int i=1;i<=n;i++){//枚举物品个数
    for(int j=m;j>=1;j--){//逆序枚举背包容量,01背包的扩展
      for(int k=0;k<=nums[i];k++){//枚举物品个数
        if(j>=k*v[i]){//如果背包容量大于此时物品重量
          dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);//可以选择,并且判断选还是不选最优
        }
      }
    }
  }
  printf("%d",dp[m]);
  return 0;
}

时间复杂度有着三重for循环O(nms),时间复杂度挺高了,有没有优化一点的解法,那肯定是有的,利用二进制优化


二进制优化解法

多重背包问题的二进制优化是一种常用的优化方法,它将多个相同的物品拆分成若干份,每份数量为2^k个。这样,问题就转化为了一个0/1背包问题,可以用0/1背包问题的解法来处理。

具体步骤如下:


1.拆分物品: 对于每种物品i,如果s[i]小于等于2^k,直接将这s[i]个物品当作一个整体处理。否则,拆分成若干份,每份数量为2^k,直到处理完所有数量。

2.转化为0/1背包问题: 将每个拆分后的子物品视为一个新的物品,其重量和价值分别为原物品的重量和价值乘以拆分的数量。这样,问题就被转化为一个0/1背包问题。

3.求解0/1背包问题: 使用动态规划等方法来解决新的0/1背包问题。

4.合并解: 将得到的解合并起来,得到原多重背包问题的解。

这种方法的优点在于将多重背包问题转化为了0/1背包问题,利用了0/1背包问题的解法,同时减小了问题规模。这对于规模较大的问题可以提高求解效率。

这个问题的动态规划状态转移方程和处理方法会因为引入了数量而有所不同,但基本思路仍然是在原问题的基础上进行扩展。


主要利用了二进制特点,由2的k次方可以表示任意数,比如:1,2,4,8,16……;我想表示5,可以取一个1一个4表示。

#include<iostream>
using namespace std;
int dp[1001],w[1001],c[1001];
int n,m,t;
int a,b,nums;
int main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++){
    cin>>a>>b>>nums;
    for(int j=1;j<=nums;j<<=1){//这下面就是二进制分解
      w[t]=j*a;//把num分解成1,2,4,8……乘上对应的重量和价值
      c[t++]=j*b;
      nums-=j;//分解出来就要减去
    }
    if(nums){//如果num还有剩余,让剩余的自己单独成一项
      w[t]=nums*a;
      c[t++]=nums*b;
      nums=0;//剩余的自成一项,用完了就是0了
    }
  }
  for(int i=0;i<t;i++){//下面就是01背包解法了
    for(int j=m;j>=w[i];j--){
      dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
    }
  }
  printf("%d",dp[m]);
  return 0;
}

时间复杂度O(nmlogs),对于一些提高题来说,可能还过不了,那就再优化一下,单调队列优化。


单调队列优化解法

多重背包问题的单调队列优化解法是一种高效的解法,它通过维护一个单调队列来降低动态规划的时间复杂度。这个方法的核心思想是通过队列保存物品的信息,以减少重复计算。

以下是单调队列优化解决多重背包问题的基本步骤:


1.将每种物品展开: 将每种物品按照数量展开成若干个单独的物品。这样,问题就转化为了一个普通的0/1背包问题。

2.使用单调队列: 对于每个物品,维护一个单调递减的队列,队列中存储的是物品的编号。队列头部的物品拥有最大的价值。

3.动态规划: 使用动态规划的思想来更新队列。遍历背包容量范围,对于每一个容量,从队列头部取出物品,更新当前状态的最大价值。然后将当前物品放回队列,保持队列的单调性。

4.重复处理: 重复处理每个物品,直到处理完所有物品。


这种方法的优势在于通过单调队列的维护,避免了对于相同子问题的重复计算,从而提高了算法的效率。在处理大规模多重背包问题时,单调队列优化是一个有效的解决方案。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e3+5,M=2e4+5;
int f[M],g[M],v[N],w[N],s[N],q[M];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
      memcpy(g,f,sizeof(f));//因为要正序遍历背包容量了,复制一个f数组g,保证在遍历i+1个物品时,状态由g[i]转移过来
        cin>>v[i]>>w[i]>>s[i];
        for(int j=0;j<v[i];j++)//分解成v[i]个类
        {
            int head=0,tail=-1;
            for(int k=j;k<=m;k+=v[i])//对每个类使用单调队列,s[i]的数就是滑动窗口的宽度
            {
                if(head<=tail&&q[head]<k-s[i]*v[i])head++;//q[h]不在窗口[k-s[i]*v[i],k-v[i]]之内,在其左侧,队头要出队
                if(head<=tail)f[k]=max(g[k],g[q[head]]+(k-q[head])/v[i]*w[i]);//使用队头最大值更新f,(k-q[head])/v[i]表示还能放入的物品个数
                //f[k]通过前面的旧值g[q[head]]来更新,所以窗口在g数组上滑动,f[k]=窗口中最大值+还能放入物品的价值
                while(head<=tail&&g[k]>=g[q[tail]]+(k-q[tail])/v[i]*w[i])tail--;//当前值大于队尾值,队尾出队
                q[++tail]=k;//下标入队,便于对头出队
            }
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

时间复杂度O(nm),时间复杂度大大降低,笔者认为单调队列优化比较难懂,很抽象,多看几遍,可以用视频辅助学习,笔者从B站找了两个讲的比较好的视频。

E13 背包DP 多重背包 单调队列优化_哔哩哔哩_bilibili

多重背包——单调队列优化_哔哩哔哩_bilibili

由于笔者水平有限,理解的不是很透彻,写得也不是很好,一些方面可能也存在着问题,望大家理解支持,有错误就指出改正,大家一起进步,下篇更新完全背包问题

相关文章
|
存储 分布式计算 大数据
大数据处理流程包括哪些环节
大数据处理流程作为当今信息时代的关键技术之一,已经成为各个行业的必备工具。这个流程涵盖了从数据收集、存储、处理、分析到应用的各个环节,确保了数据的有效利用和价值的最大化。
|
Serverless
MATLAB-常见插值方法及常见知识
MATLAB-常见插值方法及常见知识
917 0
MATLAB-常见插值方法及常见知识
|
数据挖掘 Python
Pandas时间序列处理:日期与时间
本文介绍Pandas在处理时间序列数据时的基础概念、常见问题及解决方案。涵盖时间戳、时间间隔和周期等概念,详细讲解日期格式转换、缺失值处理、时间间隔计算和重采样等操作,并通过代码示例说明如何解决`ParserError`和`OutOfBoundsDatetime`等常见报错。掌握这些知识有助于高效处理时间序列数据,提高数据分析的质量和效率。
855 75
|
9月前
|
机器学习/深度学习 算法 5G
【MUSIC、最大似然与克拉美-罗下界】MUSIC与ESPRIT 算法来估计到达角(AoA),并尝试推导克拉美-罗下界(CRLB)以分析其性能研究(Matlab代码实现)
【MUSIC、最大似然与克拉美-罗下界】MUSIC与ESPRIT 算法来估计到达角(AoA),并尝试推导克拉美-罗下界(CRLB)以分析其性能研究(Matlab代码实现)
634 0
|
存储 人工智能 运维
自我提升可以从哪些方面:AI时代的能力重构与终身进化
在数字技术与AI快速发展的背景下,自我提升从“阶段式学习”转变为“持续性进化”。文章从认知升级、技能进化、生态构建三个维度解析AI时代个人能力提升的核心路径。强调个体需从知识积累转向能力重构,通过批判性思维、跨域关联和动态适应性实现思维跃迁;同时构建复合能力体系,并借助AI工具与协作网络,在数字化转型中扮演价值创造者角色。最终,自我提升将超越传统框架,成为能力生态的动态演进过程。
|
机器学习/深度学习 算法 数据处理
SVM的优缺点是什么
SVM的优缺点是什么
1531 9
|
存储 人工智能 算法
【算法总结】拓扑排序
【算法总结】拓扑排序
1267 0
|
数据库 数据库管理
理解数据库的ACID原则:确保数据完整性与一致性的基石
【5月更文挑战第20天】ACID原则是数据库事务处理的核心,包括原子性、一致性、隔离性和持久性。原子性保证事务操作全完成或全不完成,保持数据完整;一致性确保事务前后数据库保持一致性状态,不破坏完整性约束;隔离性防止并发事务相互影响,通过锁等技术实现;持久性则保证事务提交后的修改永久保存,即使系统故障也能恢复。这些原则确保了数据的可靠性和安全性。
1442 3
|
Python
Python使用isinstance()函数
【5月更文挑战第10天】Python使用isinstance()函数
1185 2
|
Web App开发 缓存 运维
CentOS命令大全:从入门到精通
CentOS命令大全:从入门到精通
2271 1

热门文章

最新文章