本文仅记录个人对背包问题的一些理解,仅做记录用
// v体积 w价值 s数量 N物品种类 C背包大小
function maxValue(N: number, C: number, s: number[], v: number[], w: number[]) {
// 处理 0-C ,故有C+1个
const dp: number[] = Array.from({ length: C + 1 }).fill(0)
// 遍历物品
for (let i = 0; i < N; i++) {
const vi = v[i] // 体积
const wi = w[i] // 价值
const si = s[i] // 数量
// 遍历体积余数
for (let y = 0; y < vi; y++) {
// 创建dp的copy,此处便利过程中不需要修改
const oldDp = [...dp]
// 单调递减序列,oldDp下标
const dj = []
// 遍历余数对体积的等差数列
for (let num = y; num <= C; num += vi) {
// 超标,移除第一个
if (dj[0] !== undefined && dj[0] < num - si * vi) {
dj.shift()
}
// 遍历dj最后一个的价值和当前的价值对比,直到超过当前的价值
while (
dj.length &&
oldDp[dj[dj.length - 1]] - ((dj[dj.length - 1] - y) / vi) * wi <=
oldDp[num] - ((num - y) / vi) * wi
) {
dj.pop()
}
// 将当前值添加到dj中
dj.push(num)
// 计算当前最佳
dp[num] = Math.max(oldDp[num], oldDp[dj[0]] + ((num - dj[0]) / vi) * wi)
}
}
}
return dp[C]
}
// N = 2, C = 5, v = [1,2], w = [1,2], s = [2,1]
console.log(maxValue(2, 5, [1, 2], [1, 2], [2, 1]))
// 4
通过单调递减队列记录某一个物品新增个数是,是否值得加入 如果更值,则移除单调队列最后不值的,如果不值得,则直接添加到单调队列最后
其中最难理解的是while循环部分,比较是否更值
oldDp记录了上一件物品放入背包的最高价值
当新物品到第1件时,有如下等式,V是放入前占用的体积
newDp[V + vi] = max(oldDp[V + vi], oldDp(V) + wi)
于是有oldDp[V + vi] >= oldDp(V) + wi时,当前物品不放入更优
调整一下oldDp[V + vi] - wi >= oldDp(V) (1
同样的,oldDp[V + 2vi] - 2wi >= oldDp(V)
两边同时加oldDp[V + vi]得oldDp[V + 2vi] - 2wi + oldDp[V + vi] >= oldDp(V) + oldDp[V + vi] (2
(2 - (1得
oldDp[V + 2vi] - 2wi >= oldDp(V + vi) - wi >= oldDp(V)
...递归出更多不等式
当不等式成了时,表示选oldDp[V + nvi]比选oldDp[V + mvi] (n > m)更优,所以移除单调队列最后一个