@RabbitHu
2021-12-11T20:43:31.000000Z
字数 1763
阅读 3342
题解
有种物品,每种物品的数量为。从中任选若干件放在容量为的背包里,每种物品的体积为(为整数),与之相对应的价值为(为整数)。求背包能够容纳的最大价值。
第1行,2个整数,和中间用空格隔开。为物品的种类,为背包的容量。(1 <= <= 100,1 <= <= 50000)
第2 - + 1行,每行3个整数,, 和 分别是物品体积、价值和数量。(1 <= <= 10000, 1 <= <= 200)
输出可以容纳的最大价值。
多重背包。
首先写出朴素的状态转移方程(表示前个物品、总体积为):
直接这样算的复杂度最差可达(极大、极小时)。
那么我们如何优化呢?考虑为、为、为时的计算过程:
提出一些,将以上三式整理一下:
可以发现,以上三式的计算中有许多重复的地方。
那么如何消除重复,达到优化呢?观察原始方程:
设,则上式等价于:
那么在每个i循环中,可以把所有的j按a分成w[i]组,每组新建一个队列,枚举b,每次把放进队列,并保持队列长度不超过c[i],用O(1)求出队列中最大值、从最大值转移即可。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 103, M = 50003;
int n, m, w[N], p[N], c[N], ans;
//w[i]: 体积; p[i]: 价值; c[i]: 数量
int dp[M], qa[M], qb[M], la, lb, ra, rb;
//qa: 正常队列; qb: 用来求最大值的辅助队列
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%d%d%d", &w[i], &p[i], &c[i]);
for(int i = 1; i <= n; i++){
for(int j = 0; j < w[i]; j++){
la = lb = 0, ra = rb = -1;//清空队列
for(int k = 0; j + k * w[i] <= m; k++){
if(ra - la + 1 > c[i])//如果队列中元素个数超过限制
if(qb[lb] == qa[la++]) lb++; //从队首弹出
int x = dp[j + k * w[i]] - k * p[i];
qa[++ra] = x;
while(rb >= lb && qb[rb] < x) rb--;
qb[++rb] = x;
dp[j + k * w[i]] = qb[lb] + k * p[i];
ans = max(ans, dp[j + k * w[i]]);
}
}
}
printf("%d\n", ans);
return 0;
}