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

23 篇文章 0 订阅

AcWing 6
参考题解

题意

输入n个物品,背包体积为m。
输入顺序是, V i , w i , s i V_i, w_i, s_i Vi,wi,si。分别代表第i件物品的体积,价值,和数量。

理解

  1. 可以先看上面蓝色字体的“题解”。
  2. 单调队列优化相较于二进制优化,单调队列优化需要对多重背包有着更深入的了解。其更难更抽象。且需要熟练“滑动窗口”该题的数组模拟方法。
  3. 单调队列中存的是数据的下标,而不是数据本身,主要是为了起到“限制窗口的大小”的作用。下面给出的模板中的单调队列中,当头指针(head)大于尾指针(tail)时,队列为空。
  4. 了解并较好的理解多重背包的一般写法。
//第二维循环地位基本与第一维循环齐平,可以看作c[i]个物品的01背包。
//总体上:可以看作n类物品总和的01背包。
for(int i = 1; i <= n; i++)//物品种类
	for(int j = 1; j <= c[i]; j++)//物品数量
		for(int k = m; k >= v[[i]; k--)//决策
			dp[k] = max(dp[k], dp[k - v[i]] + w[i]);
  1. 以下是对于参考题解的理解
  • 单调队列中元素个数为 s + 1 个。即 0 ~ s 个物品 i。
  • 注意:
    每次入队的是dp[j+k*v] - k*w的下标j + k * v.
    while循环中w前面的系数等于j增加的v的数量(推导详见参考题解),系数为 “” 。
  • 第二个if中while的系数之所以为“”,与定义中的状态转移方程类似,就是通过单调队列已经知道最优情况下第i类物品选多少个了(选(k - q[head]) / v个第i类物品);然后是直接对其进行状态转移,选(k - q[head]) / v个第i类物品,所以要加上(k - q[head]) / v * w的价值。

代码

const int N = 2e4 + 7;
int dp[N],pre[N], q[N];

int main()
{
	IOS;

	int n, m; cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		int v, w, s;
		cin >> v >> w >> s;

		memcpy(pre, dp, sizeof dp);
		for (int j = 0; j < v; j++)
		{
			int head = 0, tail = -1;
			for (int k = j; k <= m; k += v)
			{
				//维护“窗口的长度”
				if (head <= tail && k - s * v > q[head])
					head++;
				
				//维护队列的单调性
				while (head <= tail && pre[k] - (k - j) / v * w >= pre[q[tail]] - (q[tail] - j) / v * w)
					tail--;
				
				if (head <= tail) dp[k] = max(dp[k], pre[q[head]] + (k - q[head]) / v * w);
				q[++tail] = k;//此步要位于上一步之后
			}
		}
	}
	cout << dp[m] << endl;

	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

to cling

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

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

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

打赏作者

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

抵扣说明:

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

余额充值