ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

动态规划多重背包问题的单调队列优化详解

动态规划多重背包问题的单调队列优化详解

1. 多重背包问题与单调队列优化概述

多重背包问题是动态规划中的经典问题,它介于01背包和完全背包之间。具体描述为:给定n种物品和一个容量为V的背包,第i种物品最多有s_i件可用,每件体积是w_i,价值是v_i。我们需要选择装入背包的物品,使得总体积不超过背包容量,且总价值最大。

传统解法使用三重循环:

dp = [0] * (V + 1) for i in range(n): # 遍历物品 for j in range(V, w[i] - 1, -1): # 遍历背包容量 for k in range(1, min(s[i], j // w[i]) + 1): # 遍历物品数量 dp[j] = max(dp[j], dp[j - k*w[i]] + k*v[i])

这种方法时间复杂度为O(nVS),当数据量较大时效率很低。

2. 单调队列优化原理详解

2.1 状态转移方程的重新表述

我们发现对于每个物品i,dp[j]只与dp[j - k*w[i]]有关,其中k∈[1, s_i]。可以将状态按w_i的余数分类,对于余数相同的j,它们之间可以形成独立的转移链。

设j = m*w_i + r,其中r = j % w_i,则状态转移方程可以改写为:

dp[m*w_i + r] = max{dp[k*w_i + r] + (m-k)*v_i} 其中k∈[m-s_i, m]

2.2 单调队列的引入

对于每个余数r,我们维护一个单调队列,队列中存储的是可能成为最优解的候选状态。具体来说,队列中的元素k应该满足:

  1. k在窗口[m-s_i, m]内
  2. dp[kw_i + r] - kv_i尽可能大

队列维护两个性质:

  1. 单调性:dp[kw_i + r] - kv_i递减
  2. 时效性:k在有效窗口内

2.3 具体实现步骤

  1. 按余数r分类处理
  2. 对于每个r,初始化单调队列
  3. 遍历m从0到(V-r)/w_i
  4. 维护队列的单调性和时效性
  5. 计算当前最优值

3. 优化后的算法实现

3.1 Python实现代码

def multiple_knapsack(n, V, w, v, s): dp = [0] * (V + 1) for i in range(n): for r in range(w[i]): q = [] for m in range((V - r) // w[i] + 1): current = m * w[i] + r # 维护队列时效性 while q and m - q[0] > s[i]: q.pop(0) # 计算候选值 candidate = dp[current] - m * v[i] # 维护队列单调性 while q and dp[q[-1]*w[i]+r] - q[-1]*v[i] <= candidate: q.pop() q.append(m) # 更新dp值 if q: dp[current] = max(dp[current], dp[q[0]*w[i]+r] + (m - q[0]) * v[i]) return dp[V]

3.2 复杂度分析

优化后算法的时间复杂度降为O(n*V),空间复杂度为O(V)。相比原始的三重循环,效率有了显著提升。

4. 实际应用中的注意事项

4.1 边界条件处理

  • 当w[i]=0时需特殊处理
  • 确保队列不为空时再访问队首元素
  • 注意物品数量s[i]可能为0的情况

4.2 性能优化技巧

  1. 预处理物品:合并相同w和v的物品
  2. 二进制优化:将多重背包转化为01背包
  3. 滚动数组:减少空间使用

4.3 常见错误排查

  1. 队列维护顺序错误:应先处理时效性再处理单调性
  2. 余数循环范围错误:应为range(w[i])而非range(min(w[i], V))
  3. 物品数量限制未正确应用:确保k在[m-s_i, m]范围内

5. 与其他优化方法的对比

5.1 与二进制优化的比较

二进制优化将多重背包转化为O(logS)个01背包问题,时间复杂度为O(nVlogS)。当S较大时,单调队列优化更优。

5.2 与滑动窗口优化的关系

单调队列优化本质上是滑动窗口优化在背包问题中的应用,但需要结合背包问题的特殊性质进行调整。

5.3 适用场景分析

  • 单调队列优化适合单个物品数量较大的情况
  • 二进制优化适合物品数量差异大的情况
  • 当V较小时,原始方法可能更简单直接

6. 扩展应用与变种问题

6.1 多维背包问题

对于多维背包(如同时限制体积和重量),单调队列优化可以沿一个维度进行,另一个维度仍使用传统方法。

6.2 分组背包问题

当物品分为若干组,每组只能选一个物品时,可以在组内使用单调队列优化。

6.3 带依赖的背包问题

对于树形依赖的背包问题,可以在DFS过程中对每个节点应用单调队列优化。

在实际编码比赛中,我通常会先写出朴素解法确保正确性,然后再考虑是否适用单调队列优化。特别是在物品数量限制较大(如s_i≥100)时,这种优化能带来显著的性能提升。一个常见的调试技巧是打印出单调队列在每次迭代时的状态,这能帮助快速定位问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进