Acwing6多重背包Ⅲ_多重背包+单调队列优化

题目链接:https://www.acwing.com/problem/content/6/
解题过程:
首先原始的多重背包问题的最原始的做法是直接开三重循环,然后进一步优化是通过二进制将其转换成0/1背包去做,这里我用的方法是通过单调队列去优化原始的三重循环,时间复杂度为0(n * m);这道题目是:2e7,不会超时。
首先我们需要知道这样的算式:
f[i, j] = max(f[i - 1, j], f[i - 1, j - v]+w, f[i - 1, j - 2v]+2w, … , f[i - 1, j - sv]+sw)
f[i, j - v] = max( , f[i - 1, j - v], f[i - 1, j - 2v]+ w, …, f[i - 1, j- sv]+(s - 1)w, f[i - , j - (s+1)v]+sw)
当我们使用原始的做法的时候,对于每一个f[i - kv], 我们是直接通过遍历的方法去求其最大数值,通过单调队列到达的目的就是将这一重循环给去掉,通过上面的算式,我们发现,对于f[i, j]来说,其实有很大一部分与f[i, j - v]是重复的,他们的差距仅仅只是相差一个w, 这个是可以通过相减来处理掉的,并且由于是求最大数值,所以刚好想到可以用单调队列来写。
代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int maxn = 20005;

int f[maxn], g[maxn], q[maxn];

/*
f[k]:f[i, k] 
g[k]:f[i - 1, k] 
q[]:是一个数组模拟的单调队列 
*/

int main(void) {
	int n, m; scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++) {
		int v, w, s; 
		scanf("%d%d%d", &v, &w, &s);
		memcpy(g, f, sizeof f);        //每次都将f复制道g中,这样在计算f[i, k]地时候,就可以直接调用g数组 
		
		//这里采用的方法是,遍历所有可能的余数来进行dp;与原始的遍历方法有一点理解上的细微差别 
		
		for(int j = 0; j < v; j ++) {
			int hh = 0, tt = -1;      //hh:队头, tt:队尾 
			
			for(int k = j; k <= m; k += v) {
				//判断当前队头元素是否已经出列,注:队列中存储的是体积,相当于下标 
				if(hh <= tt && k - s * v > q[hh]) hh ++;      
				//在队列不为空的前提下,判断新进入的元素与队尾元素进行比较,若是前者更大,则将队尾元素出队
				//为什么这里是 - 号,原因:我们可以去看写出的算式,发现越是体积越大的时候,其实越后面才会加一个w,
				//刚好构成了一个等差数列,那么我们如果通过这个相对w之差,则刚好等价于原来相加的情况 
				while(hh <= tt && g[q[tt]] - q[tt] / v * w <= g[k] - k / v * w) tt --;      
				//入队 
				q[++ tt] = k;
				//将队头元素放进f中,队头元素:此时的最大数值 
				f[k] = g[q[hh]] + (k - q[hh]) / v * w;
			}
		}
	} 
	
	printf("%d\n", f[m]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值