多重背包单调队列优化详解

公式

  • 背包问题的思考方向不是有多大的包要选哪些物品,而是腾出足够的大小放物品
dp[i][j] = max{dp[i-1][j-k*vi]+k*wi} 0<=k<=ni && k*vi<=j
vi,wi,ni分别是物品i的体积,价值和个数,j是当前背包的容积
  • 展开第i个物体的公式:
dp[i][0] =          dp[i-1][0]
dp[i][1] =          dp[i-1][1]
...
dp[i][vi-1] =       dp[i-1][vi-1]
//----------------------------------------------------------------------------
dp[i][vi] = max{    dp[i-1][vi],     dp[i-1][0]+wi}
dp[i][vi+1] = max{  dp[i-1][vi+1],   dp[i-1][1]+wi}
...
dp[i][2*vi-1] = max{dp[i-1][2*vi-1], dp[i-1][vi-1]+wi}
//---------------------------------------------------------------------------
dp[i][2*vi] = max{  dp[i-1][2*vi],   dp[i-1][vi]+wi,     dp[i-1][0]+2*wi}
dp[i][2*vi+1] = max{dp[i-1][2*vi+1], dp[i-1][vi+1]+wi,   dp[i-1][1]+2*wi}
...
dp[i][3*vi-1] = max{dp[i-1][3*vi-1], dp[i-1][2*vi-1]+wi, dp[i-1][vi-1]+2*wi}
//--------------------------------------------------------------------------
dp[i][3*vi] = max{  dp[i-1][3*vi],   dp[i-1][2*vi]+wi,   dp[i-1][vi]+2*wi, dp[i-1][0]+3*wi}
...
  • 可以看出后vi个会在前vi个的基础上多加一项求最值,以vi为步长,需要的dp数组的元素高度重复
  • 比如
dp[i][0] =          dp[i-1][0]
dp[i][vi] = max{    dp[i-1][vi],     dp[i-1][0]+wi}
dp[i][2*vi] = max{  dp[i-1][2*vi],   dp[i-1][vi]+wi,     dp[i-1][0]+2*wi}
dp[i][3*vi] = max{  dp[i-1][3*vi],   dp[i-1][2*vi]+wi,   dp[i-1][vi]+2*wi, dp[i-1][0]+3*wi}
  • 上述只涉及dp的四个元素:dp[i-1][0],dp[i-1][vi],dp[i-1][2*vi],dp[i-1][3*vi]。同时因为求的是相对大小,可以同时减去k*wi转化一下,即变成
dp[i][0] =          dp[i-1][0]
dp[i][vi] = max{    dp[i-1][vi]-wi,       dp[i-1][0]}
dp[i][2*vi] = max{  dp[i-1][2*vi]-2*wi,   dp[i-1][vi]-wi,       dp[i-1][0]}
dp[i][3*vi] = max{  dp[i-1][3*vi]-3*wi,   dp[i-1][2*vi]-2*wi,   dp[i-1][vi]-wi, dp[i-1][0]}
  • 显然,每vi个求最大值可以使用单调队列优化
  • 因为dp[i]只需要dp[i-1]的数据,可以空间优化,使用一维数组
  • 伪代码
for 物品i:
	for j in 0...vi-1:
		for k in 0...(总容积-j)/vi:
			dp[k*vi+j]-k*wi进队
			队首的k值小于当前循环的k-ni,队首出队
			dp[k*vi+j]=队首+k*wi

代码

#include <deque>
#include <vector>
#include <iostream>

using namespace std;

struct lwd {
	int val;
	int k;
	lwd(int v, int kk) {val=v, k=kk;}
};

int multipack(vector<int> v, vector<int> w, vector<int> n, int pack) {
	vector<int> dp(pack+1);
	for(int i = 0; i < v.size(); ++i) {
		for(int j = 0; j < v[i]; ++j) {
			deque<lwd> dq;
			// 最多能放物品i的个数
			int end = (pack-j)/v[i];
			for(int k = 0; k <= end; ++k) {
				int val = dp[j+k*v[i]] - k*w[i];
				while(!dq.empty() && dq.back().val <= val) dq.pop_back();
				dq.push_back(lwd(val, k));
				// 最多有ni个物品i
				if(dq.front().k < k-n[i]) dq.pop_front();
				dp[j+k*v[i]] = dq.front().val + k*w[i];
			}
		}
	}
	return dp[pack];
}

int main() {
    int N=4;
    vector<int> v(N), w(N), n(N);
    for(int i = 0; i < N; ++i) {
        v[i] = i+1;
    }
    w[0]=2, w[1]=w[2]=4, w[3]=5;
    n[0]=3, n[1]=1, n[2]=3, n[3]=2;
    cout << multipack(v,w,n,5) << endl;
    return 0;
}

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

刀么克瑟拉莫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值