题解:P1782 旅行商的背包
Part1 总体思路
普通物品与奇物的选择除了共用体积外没有限制,所以可以分别结算,使用
Part2 普通物品
这是典型的完全背包模板,数据规模较大,把每个物品拆成
首先我们考虑一个体积为
为了使用单调队列,相邻转态的可选决策集合(意思就是可以从哪些状态转移而来)必须有重合且对应的价值单调以便直接选择最优解,显然相邻的两个体积(相差为1)不会存在重合的决策(除非
但如果只考虑状态
-
注意:此处及后文书写的集合或方程都没有考虑部分边界条件,仅作推导,具体实现须考虑边界。
-
tips:其中
k 的含义是选择k 个物品。(不选此物品时(即k = 0 情况),由于使用一维数组,直接继承原来的值即可,无需计算)。
将上述集合稍作变形得到:
- tips:其中
k 的含义是选择(p-k) 个物品。
从中可以发现状态
进而得到状态转移方程,对于每一个余数
为了保证物品最多选
接下来讨论如何设计单调队列
每次
- tips:接下来使用
q_{l} 与q_{r} 分别表示队头与队尾,\operatorname{calc}(k) 表示(f_{u + k \times V} - k \times W) 。
在每次转移后,我们需要加入新增决策
在下一次转移前,我们需要保证队头的
接下来直接使用
- 注意:在倒序循环
p 前需要初始化队列。
把上述操作扩展到所有普通物品和每个余数即可。程序留作课后习题,读者完成不难。
Part3 奇物
考虑到数据规模较小(
- 不能选同一种奇物选出多个体积单独计算价值后装入背包,必须由装入的总体积计算价值,所以要使用类似 0/1背包 的倒序循环目标体积
j 。 - 而对于每个目标体积
j 状态转移方程应为f_{j} = \max_{0 \le V \le C}(f_{j - V} + \operatorname{W}(i, V)) ,注意从V = 0 开始枚举。 - 特别的,
\operatorname{W}(i,0) = \max(c_{i}, 0) ,表示若体积V = 0 时的收益为负可以选择不装。
此外,还有基于二次函数性质的小优化,根据二次函数的单调性,对于每个奇物以对称轴 (懒得写了)。
Part4 后记
看到其他题解说单调队列优化过不了此题,我表示不服,遂作此题解 (其实主要是想借题发挥讲一讲单调队列优化),更多是对单调队列优化完全背包的详解。但单调队列常数确实很大,考虑缓存未命中导致的效率下降,估算时间复杂度时建议将常数当
如果单调队列实在写不出来这里提供参考程序(未定义变量见上文含义):
#define calc(k) (f[u + (k) * a[i].v] - (k) * a[i].w)
int q[2005];
for (int i = 1; i <= n; ++i) {
for (int u = 0; u < a[i].v; ++u) {
int l = 1, r = 0;
int maxp = (c - u) / a[i].v;
for (int k = maxp-1; k >= maxp - a[i].d && k >= 0; --k) {
while (l <= r && calc(q[r]) <= calc(k)) --r;
q[++r] = k;
}
for (int p = maxp; p >= 0; --p) {
while (l <= r && q[l] > p-1) ++l;
if (l <= r) f[u + p*a[i].v] = max(f[u + p*a[i].v], calc(q[l]) + p*a[i].w);
if (p - a[i].d - 1 >= 0) {
while (l <= r && calc(q[r]) <= calc(p-a[i].d-1)) --r;
q[++r] = p - a[i].d - 1;
}
}
}
}
最后,点击查看AC记录