多重背包(单调队列优化)

话不多说,直接上题,先看一道单调队列的经典题,理解一下单调队列

(大佬跳过)

题目链接

AC代码

#include <iostream>
using namespace std;
const int M = 1000100;
int que[M], head, tail, ans1[M], ans2[M], que2[M];
int map1[M];
int main()
{
	int n, k;
	cin >> n >> k;
	for (int i = 0; i < n; ++i) {
		cin >> map1[i];
	}
	int cnt = 0, cnt2 = 0;
	// 为了方便判断队头出框,所以队列中存储map1下标
	head = 0;
	tail = -1;
	for (int i = 0; i < n; ++i) {
		if (head <= tail && i - que[head] > k - 1) head++;		// 1.防止队列为空     2.弹出队头(队头后移)
		while (head <= tail && map1[i] >= map1[que[tail]]) tail--;		// 1.防止队列为空     2.因为如果前面的数比要加入的数要小,那么必定不能作为最大值,所以弹出  维护队列单调性
		que[++tail] = i;		// 1.将map1下标放入队尾 如果之前已经将队删除干净,则新加入的为队头,如果没删除干净则目前最大的还是队头
		if (i >= k - 1)
			ans1[cnt++] = map1[que[head]];
		// 细品
	}
	head = 0;
	tail = -1;
	// 类似上边,不过一个队头是最大,一个队头是最小
	for (int i = 0; i < n; ++i) {
		if (head <= tail && i - que2[head] > k - 1) head++;
		while (head <= tail && map1[que2[tail]] >= map1[i]) tail--;
		que2[++tail] = i;
		if (i >= k - 1)
			ans2[cnt2++] = map1[que2[head]];
	}
	for (int i = 0; i <= n - k; ++i) {
		cout << ans2[i] << " ";
	}
	cout << endl;
	for (int i = 0; i <= n - k; ++i) {
		cout << ans1[i] << " ";
	}
	cout << endl;
	return 0;
}

背包总容量为V,背包中物品数量为N
每件物品质量或者体积为w,价值为v,数量为s
不用单调队列优化的情况下,对于每一个物品,应该是每次求出
0 <= j <= V
dp[j] = max(dp[j], dp[j - w] + v, dp[j - 2 * w] + 2 * v, … , dp[j - k *w] + k * v)。

对于每个物品我们可以将背包的0 - V分为 w个组
1组: dp[0], dp[0 + w], dp[0 + 2 * w], … , dp[0 + k * w]
2组: dp[1], dp[1 + w], dp[1 + 2 * w], … , dp[1 + k * w]
3组: dp[2], dp[2 + w], dp[2 + 2 * w], … , dp[2 + k * w]
.
.
.
w组: dp[w - 1], dp[w - 1 + w], dp[w - 1 + 2 * w], … , dp[w - 1 + k * w]
对于每组,下标可以以(j + k * w)表示出,不同的组不会互相影响
dp[j] = max(dp[j])
dp[j + w] = max(dp[j + w], dp[j + w - w] + v)
dp[j + 2 * w] = max(dp[j + 2 * w], dp[j + w] + v, dp[j] + 2 * v)

dp[j + k * w] = max(dp[j + k * w], dp[j + (k - 1) * w] + v, … , dp[j + k - (k - 1) * w] + (k - 1) * v, dp[j + (k - k) * w] + k * v)
可以转化为
dp[j] = max(dp[j]) = max(dp[j])
dp[j + w] = max(dp[j + w], dp[j + w - w] + v) = max(dp[j + w] - v, dp[j])) + v
dp[j + 2 * w] = max(dp[j + 2 * w], dp[j + w] + v, dp[j] + 2 * v) = max(dp[j + 2 * w] - 2 * v, dp[j + w] - v, dp[j + 2 * w]) + 2 * v

dp[j + k * w] = max(dp[j + k * w], dp[j + (k - 1) * w] + v, … , dp[j + (k - (k - 1)) * w] + (k - 1)* v, dp[j + (k - k)] + k * v)
= max(dp[j + k * w] - k * v, dp[j + (k - 1) * w]- (k - 1) * v, …, dp[j + (k - (k - 1)) * w] - (k - (k - 1)) * v, dp[j + (k - k) * w] - (k - k) * v) + k * v
因为每次都需要加上(k * v),(k * v) 是一个常数,所以每次都只需要比较 dp[j + x * w] + x * v 就可以了,本次是dp[j + k * w],之前最小是dp[j + que[tail] * w] + que[tail] * v,利用先删后加,将不可能出现的大小删除除。
对应这一段

	while (head <= tail && dp1[j + k * w] - k * v >= dp1[j + que[tail] * w] - que[tail] * v)
					tail--;

而在修改值的时候不要忘了加上 k * v
这也是为什么比较大小的时候和修改值的时候不同的原因
例题
题目链接
代码

#include <iostream>
#include <cstring>
using namespace std;
const int M = 20020;
int dp[M], dp1[M], que[M];
int main()
{
	int N, V;
	cin >> N >> V;
	int w, v, s;
	for (int i = 0; i < N; ++i) {
		cin >> w >> v >> s;
		memcpy(dp1, dp, sizeof(dp));
		for (int j = 0; j < w; ++j) {
			int head = 0, tail = -1;
			for (int k = 0; j + k * w <= V; ++k) {
				if (head <= tail && k - que[head] > s) head++;
				while (head <= tail && dp1[j + k * w] - k * v >= dp1[j + que[tail] * w] - que[tail] * v)
					tail--;
				que[++tail] = k;
				dp[j + k * w] = dp1[j + que[head] * w] + (k - que[head]) * v;

			}
		}
	}
	cout << dp[V] << endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值