[关闭]
@RabbitHu 2021-12-11T20:43:31.000000Z 字数 1763 阅读 3342

多重背包 O(VN) 做法 | 单调队列 ##


题解


题目

种物品,每种物品的数量为。从中任选若干件放在容量为的背包里,每种物品的体积为(为整数),与之相对应的价值为(为整数)。求背包能够容纳的最大价值。

Input

第1行,2个整数,中间用空格隔开。为物品的种类,为背包的容量。(1 <= <= 100,1 <= <= 50000)
第2 - + 1行,每行3个整数, 分别是物品体积、价值和数量。(1 <= <= 10000, 1 <= <= 200)

Output

输出可以容纳的最大价值。


题解

多重背包。

首先写出朴素的状态转移方程(表示前个物品、总体积为):

直接这样算的复杂度最差可达极大、极小时)。

那么我们如何优化呢?考虑时的计算过程:



提出一些,将以上三式整理一下:



可以发现,以上三式的计算中有许多重复的地方。

那么如何消除重复,达到优化呢?观察原始方程:

,则上式等价于:

那么在每个i循环中,可以把所有的j按a分成w[i]组,每组新建一个队列,枚举b,每次把放进队列,并保持队列长度不超过c[i],用O(1)求出队列中最大值、从最大值转移即可。

  1. #include <cstdio>
  2. #include <cmath>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <iostream>
  6. using namespace std;
  7. const int N = 103, M = 50003;
  8. int n, m, w[N], p[N], c[N], ans;
  9. //w[i]: 体积; p[i]: 价值; c[i]: 数量
  10. int dp[M], qa[M], qb[M], la, lb, ra, rb;
  11. //qa: 正常队列; qb: 用来求最大值的辅助队列
  12. int main(){
  13. scanf("%d%d", &n, &m);
  14. for(int i = 1; i <= n; i++)
  15. scanf("%d%d%d", &w[i], &p[i], &c[i]);
  16. for(int i = 1; i <= n; i++){
  17. for(int j = 0; j < w[i]; j++){
  18. la = lb = 0, ra = rb = -1;//清空队列
  19. for(int k = 0; j + k * w[i] <= m; k++){
  20. if(ra - la + 1 > c[i])//如果队列中元素个数超过限制
  21. if(qb[lb] == qa[la++]) lb++; //从队首弹出
  22. int x = dp[j + k * w[i]] - k * p[i];
  23. qa[++ra] = x;
  24. while(rb >= lb && qb[rb] < x) rb--;
  25. qb[++rb] = x;
  26. dp[j + k * w[i]] = qb[lb] + k * p[i];
  27. ans = max(ans, dp[j + k * w[i]]);
  28. }
  29. }
  30. }
  31. printf("%d\n", ans);
  32. return 0;
  33. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注