多重背包 单调队列优化

参考 洛谷P1776 宝物筛选_NOI导刊2010提高(02)(多重背包,单调队列)

d p [ i ] [ j ] dp[i][j] dp[i][j] 为前 i i i 个物品,重量不超过 j j j 的最大价值, w w w 为重量, v v v 为价值, m m m 为数量, W W W 为背包大小
d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j − k w i ] + k v i } 0 < = k < = m i n ( m i , j w i ) dp[i][j] = max\{dp[i-1][j-kw_i] + kv_i\} \qquad 0<=k<=min(m_i,\frac{j}{w_i}) dp[i][j]=max{dp[i1][jkwi]+kvi}0<=k<=min(mi,wij)
去掉第一维后,简化为:
d p [ j ] = m a x { d p [ j − k w ] + k v } dp[j] = max\{dp[j-kw] + kv\} dp[j]=max{dp[jkw]+kv}
时间复杂度: O ( n m W ) O(nmW) O(nmW)


仔细观察对于一个 j j j 而言,只有 j w i \frac{j}{w_i} wij 个有效转移,那么就可以去掉很多无用的状态
j = k 1 w + d j = k_1w+d j=k1w+d ,那么枚举 d d d ,再枚举 k 1 k_1 k1 ,再枚举 k k k,复杂度是一样的
d p [ j ] = m a x { d p [ k 1 w + d − k w ] + k v } dp[j] = max\{dp[k_1w+d-kw] + kv\} dp[j]=max{dp[k1w+dkw]+kv}
= m a x { d p [ ( k 1 − k ) w + d ] − ( k 1 − k ) v } + k 1 v = max\{dp[(k_1-k)w+d] - (k_1-k)v\} + k_1v =max{dp[(k1k)w+d](k1k)v}+k1v
注意 k 1 k_1 k1 k k k 是无关的,所以可以分离出来
因此对于一个 d d d 而言,有意义的状态只有 W − d w \frac{W-d}{w} wWd 种,算上 d d d ,总的状态只有 O ( W ) O(W) O(W)


g k = d p [ k w + d ] − k v g_k = dp[kw+d] - kv gk=dp[kw+d]kv
0 < = k < = m i n ( m , j w ) 0<=k<=min(m,\frac{j}{w}) 0<=k<=min(m,wj) j w = k 1 w + d w = k 1 + d w > k 1 \frac{j}{w} = \frac{k_1w+d}{w} =k_1+\frac{d}{w} >k_1 wj=wk1w+d=k1+wdk1
m a x { 0 , k 1 − m } < = k 1 − k < = k 1 ) max\{0,k_1-m\}<=k_1-k<=k_1) max{0,k1m}<=k1k<=k1)
也就是对于每个 k 1 k_1 k1,只能从 m a x { g k ∣ k ∈ [ m a x { 0 , k 1 − m } , k 1 ] } max\{g_k|k∈[max\{0,k_1−m\},k_1]\} max{gkk[max{0,k1m},k1]} 中转移
就不用枚举 k k k了,用单调队列优化维护下标在 [ k 1 − m , k 1 ] [k_1−m,k_1] [k1m,k1] 范围内的决策值即可
时间复杂度就是惊为天人的 O ( n W ) O(nW) O(nW)

模板题:P1776 宝物筛选

#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
const int maxn = 1e2 + 5;
const int maxw = 4e4 + 5;
int n, W, dp[maxn][maxw];
int v[maxn], w[maxn], m[maxn];
int q[maxw][2];

int main() {
	scanf("%d%d", &n, &W);
	for(int i=1; i<=n; i++) {
		scanf("%d%d%d", v+i, w+i, m+i);
		for(int d=0; d<w[i]; d++) {
			int head = 1, tail = 0;
			int k1 = (W - d) / w[i];
			for(int k=0; k<=k1; k++) {
				while(head<=tail && q[tail][1]<=dp[i-1][d+k*w[i]]-k*v[i]) tail--;
				q[++tail][0] = k, q[tail][1] = dp[i-1][d+k*w[i]]-k*v[i];
				while(head<=tail && q[head][0] < k-m[i]) head++;
				dp[i][d+k*w[i]] = max(dp[i][d+k*w[i]], q[head][1] + k*v[i]);
			}
		}
	}
	printf("%d\n", dp[n][W]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值