题意
输入n个物品,背包体积为m。
输入顺序是,
V
i
,
w
i
,
s
i
V_i, w_i, s_i
Vi,wi,si。分别代表第i件物品的体积,价值,和数量。
理解
- 可以先看上面蓝色字体的“题解”。
- 单调队列优化相较于二进制优化,单调队列优化需要对多重背包有着更深入的了解。其更难更抽象。且需要熟练“滑动窗口”该题的数组模拟方法。
- 单调队列中存的是数据的下标,而不是数据本身,主要是为了起到“限制窗口的大小”的作用。下面给出的模板中的单调队列中,当头指针(head)大于尾指针(tail)时,队列为空。
- 了解并较好的理解多重背包的一般写法。
//第二维循环地位基本与第一维循环齐平,可以看作c[i]个物品的01背包。
//总体上:可以看作n类物品总和的01背包。
for(int i = 1; i <= n; i++)//物品种类
for(int j = 1; j <= c[i]; j++)//物品数量
for(int k = m; k >= v[[i]; k--)//决策
dp[k] = max(dp[k], dp[k - v[i]] + w[i]);
- 以下是对于参考题解的理解
- 单调队列中元素个数为 s + 1 个。即 0 ~ s 个物品 i。
- 注意:
每次入队的是dp[j+k*v] - k*w
的下标j + k * v
.
while循环中w前面的系数等于j增加的v的数量(推导详见参考题解),系数为 “负” 。 - 第二个if中while的系数之所以为“正”,与定义中的状态转移方程类似,就是通过单调队列已经知道最优情况下第i类物品选多少个了(选
(k - q[head]) / v
个第i类物品);然后是直接对其进行状态转移,选(k - q[head]) / v
个第i类物品,所以要加上(k - q[head]) / v * w
的价值。
代码
const int N = 2e4 + 7;
int dp[N],pre[N], q[N];
int main()
{
IOS;
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++)
{
int v, w, s;
cin >> v >> w >> s;
memcpy(pre, dp, sizeof dp);
for (int j = 0; j < v; j++)
{
int head = 0, tail = -1;
for (int k = j; k <= m; k += v)
{
//维护“窗口的长度”
if (head <= tail && k - s * v > q[head])
head++;
//维护队列的单调性
while (head <= tail && pre[k] - (k - j) / v * w >= pre[q[tail]] - (q[tail] - j) / v * w)
tail--;
if (head <= tail) dp[k] = max(dp[k], pre[q[head]] + (k - q[head]) / v * w);
q[++tail] = k;//此步要位于上一步之后
}
}
}
cout << dp[m] << endl;
return 0;
}