多重背包--单调队列实现

有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V (0<N≤1000, 0<V≤20000),用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤20000
0<vi,wi,si≤20000
输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例
10
案例分析:
此题是比较有难度的一题,看了好久才明白了一些,写下一些自己的见解。
我们线从最原始的方程入手

f[i][j] = max(f[i][j],f[i][j-k*v]+w*k)  0<=k<=s,j>=v*k;

当然,我们经过一些小小的改变就可以将他变为一维的

f[j] = max(f[j],f[j-v*k]+k*w),0<=k<=s,j>v*s

此题使用单调队列实现,首先什么是单调队列。
其实就是一个具有单调性的双端队列,此题中队列的每一个元素具有两个值,一个是编号,一个是价值。单调队列就是单调递增也就是编号越大价值越大,编号越小值越小和单调递减,也就是编号越大值越小,编号越小值越大。
在此题中,我们就用的是单调递减,也就是编号越大值越小,目的就是让最大的值处于队列首位,这样每一次去f[j]最大的值的时候就是O(1),这样就可以达到O(N*V)的时间复杂度。
由输入我们可以知道背包的总容量V,知道每一个物品的体积v,和s,V/v就是背包可以装多少个物品,但是一般V/v不是整数,会有余数,我们根据余数将这个物品分为v类,这样构成了第一层循环,然后就是V/v的整数,从1->V/v构成第二层循环,

for(int i = 0;i<v;i++){
	for(int k = j;k<=V;k+=v){
		//比如一个容积为6,的包,现在放入v = 3,w=3,s=3
		//那么他要遍历3+2+2 = 7,次
		//在放入v = 4,w = 4,s = 4,
		//那么要遍历2+2+2 = 6次,每一个物品两层遍历之和接近于容积V,总共有N个物品,所以时间复杂度接近O(N*V)
	}
}

上面这个循环也可以变一下,就是直接用V/v来做边界

for(int k = 1;k<V/v;k++){
//然后在后面的计算中转换一下,k就是物品个数,但是因为我们知道容量是连续的整数1-V,刚好可以将容量和编号统一起来,不用额外的开销和逻辑,所以还是采用上面的方法,k就是当前的容量,也是当前的编号
}

要在这两层循环中加上单调队列,

for(int i = 0;i<v;i++){
	int h=0,t = -1;//h是队列的头地址,t是队列的尾地址,每一个类用一个队列,然后在队列里面求最值,
					//这个队列也就是由两个数组构成,f[]保存值,q[],保存编号(体积)。
	for(int k = j;k<=V;k+=v){
		//在这里我们用到一个叫滑动窗口的东西,来控制物品的数量在固定背包容量所能放的个数,然后在这个窗口中去寻找最大值,这里大家可以看在B站搜索单调队列看一下一个电子科技大学同学发的讲解视频。
		if(t>=h && (k-q[h])/v>s)h++;//要保证加入物品后,这个数量要小于等于s,如果大于s那就越界了,没有那么多物品,这样队					
									//列的头就要被抛弃,因为没有那么多物品给你放,也就是头地址向后移动一个位置,保证加入																
									//当前物品后,编号(体积)在给定的窗口之中,k-q[h]/v就是要加入物品的数量
		while(t>=h && g[q[t]] <= g[k]+(q[t]-k)/v*w)t--;//要保证单调性,也就是尾地址的元素要大于当前入队的元素,否则尾元素就出队,直到满足单调性为止
		if(t>=h)f[k] = max(f[k],g[q[h]]+(k-q[h])/v*w);//然后更新f[],和上一个类中在当前的容量的价值做比较,
													  //然后将优先值入队
		q[++t] = k;//将当前的编号(体积)入队尾
	}
}

这样就处理了一个物品
然后是完整代码

#include <iostream>
using namespace std;
#include <cstring>
int g[20001], q[20001], f[20001];
int N, V;
int main()
{
    cin >> N >> V;
    while (N--)
    {
        int v,w,s;
        memcpy(g,f,sizeof(f));
        cin>>v>>w>>s;
        for(int i = 0;i<v;i++)
        {
            int h = 0 , t = -1;
            for(int k = i;k<=V;k+=v)
            {
                if(t>=h && (k-q[h])/v>s)h++;
                while(t>=h && g[q[t]] <= g[k]+(q[t]-k)/v*w)t--;
                if(t>=h)f[k] = max(f[k],g[q[h]]+(k-q[h])/v*w);
                q[++t] = k;
            }
        }
    }
    cout<<f[V];
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mxmevol

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值