例题:https://www.acwing.com/problem/content/6/
多重背包除了可以使用二进制加速,还可以使用单调队列加速,并且单调队列会更快
正常的多重背包的dp方程:
其中 k 为物品的多少, v[i]为物品的体积, w[i]为物品的价值
可以看到转移的时候,dp[i][j]只和dp[i][j-k*v[i]]有关,并且是取动态的固定区间大小的区间最值,可以考虑使用优先队列优化。
使用优先队列要求我们j由小到大遍历,因此此题不方便使用一维数组简化空间,但是可以使用滚动数组减少空间
(假如使用一维数组,那么之前计算过的数值就会无限叠加,如图j-3v会叠加到j 然后j再带着j-3v的值不断往前叠加)
使用滚动数组的时候,设
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]的贡献全部会改变,比如:
当j+=v的时候
此时优先队列里维护的东西就变了。
所以需要维护一个不受距离影响的值,具体看代码:
#include<bits/stdc++.h>
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;
}