单调队列优化多重背包问题 + 例题

例题:https://www.acwing.com/problem/content/6/

多重背包除了可以使用二进制加速,还可以使用单调队列加速,并且单调队列会更快

正常的多重背包的dp方程:

其中 k 为物品的多少, v[i]为物品的体积, w[i]为物品的价值

可以看到转移的时候,dp[i][j]只和dp[i][j-k*v[i]]有关,并且是取动态的固定区间大小的区间最值,可以考虑使用优先队列优化。

单调队列优化多重背包问题 + 例题_第1张图片

使用优先队列要求我们j由小到大遍历,因此此题不方便使用一维数组简化空间,但是可以使用滚动数组减少空间

(假如使用一维数组,那么之前计算过的数值就会无限叠加,如图j-3v会叠加到j 然后j再带着j-3v的值不断往前叠加)

单调队列优化多重背包问题 + 例题_第2张图片

使用滚动数组的时候,设

dp[2][i]     i:表示此时背包体积多少    dp数组则表示当体积为i的时候可以获得的最优解

 每次使用的时候dp[t][i] , 每次需要滚动的时候t^=1 即可

 

还有一个问题,就是优先队列如何维护数值。

假如直接维护dp[i][k] + (j-k)/v[i] * w[i]  (也就是维护dp的值),那么i增加的时候,之前的dp对dp[i]的贡献全部会改变,比如:

单调队列优化多重背包问题 + 例题_第3张图片

当j+=v的时候

单调队列优化多重背包问题 + 例题_第4张图片

此时优先队列里维护的东西就变了。

 

所以需要维护一个不受距离影响的值,具体看代码:

#include
using namespace std;
const int maxn = 2e4 + 7;
int dp[2][maxn], q[maxn]; // dp 为滚动数组, q为优先队列辅助数组
#define calc(t,i) (dp[t^1][q[i]]+((k-q[i])/v)*w)
int main() {
	int n, m;
	cin >> n >> m;
	int t = 0;
	for (int i = 1; i <= n; i++) {
		int v, w, s;
		scanf("%d %d %d", &v, &w, &s);
		t ^= 1; //数组滚动
		for (int j = 0; j < v; j++) {
			int l = 1, r = 0;
			for (int k = j ; k <= m; k += v) {
				while (l <= r && (k-q[l])/v > s) l++; // 排除太远的
				if(l<=r) dp[t][k] = max(dp[t^1][k], calc(t,l));
				while (l <= r && dp[t ^ 1][q[r]] - (q[r] - j) / v * w <= dp[t ^ 1][k] - (k - j) / v * w) r--;//消除距离影响
				q[++r] = k;
			}
		}
	}
	cout << dp[t][m] << endl;
	return 0;
}

 

你可能感兴趣的:(acm,acm,优先队列,多重背包)