前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >背包九讲——多重背包问题

背包九讲——多重背包问题

作者头像
摆烂小白敲代码
发布2024-10-19 09:40:10
320
发布2024-10-19 09:40:10
举报
文章被收录于专栏:学习

背包问题第二讲——多重背包问题 背包问题是一类经典的组合优化问题,通常涉及在限定容量的背包中选择物品,以最大化某种价值或利益。问题的一般描述是:有一个背包,其容量为C;有一组物品,每个物品有重量w和价值v。目标是选择一些物品放入背包,使得它们的总重量不超过背包容量,同时总价值最大。 多重背包问题则是每个物品都是有限个,而不是只有一个。

多重背包问题

多重背包问题是背包问题的一种扩展,与0/1背包问题和分数背包问题有些不同。在多重背包问题中,每种物品都有限定的数量,不再是仅有一个,而是有多个。问题的描述如下: 给定一个背包容量为C,有n种物品,每种物品有重量w[i]、价值v[i]和数量s[i]。目标是选择物品放入背包,使得它们的总重量不超过背包容量,并且总价值最大。 这个问题在一些应用中更为真实,例如购物时某种商品有多件可供选择。解决多重背包问题的方法通常是在0/1背包问题的基础上进行一些调整。


朴素解法:

朴素经典解法就是暴力求解,通过三重for循环,第一层枚举物品个数,第二层枚举背包容量(逆序),第三层枚举物品个数来进行求解。

代码实现:
代码语言:javascript
复制
#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),n表示物品的个数,m表示背包容量,s表示第i物品个数,时间复杂度非常高了,一般不能解决任何多重背包问题,有没有优化一点的解法,那肯定是有的,利用二进制进行优化。


二进制优化解法

多重背包问题的二进制优化是一种常用的优化方法,它将多个相同的物品拆分成若干份,每份数量为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表示。其中任何一个数都能通过二进制数的加法进行得到。

代码实现:
代码语言:javascript
复制
#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.重复处理: 重复处理每个物品,直到处理完所有物品。

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

代码实现:
代码语言:javascript
复制
#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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-10-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 多重背包问题
    • 朴素解法:
      • 代码实现:
    • 二进制优化解法
      • 代码实现:
    • 单调队列优化解法
      • 代码实现:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档