返回

多重背包の单调优化——算法中的闪电侠

人工智能

单调优化:多重背包问题的终极加速器

各位算法与数据结构的爱好者们,大家好!

我们又回来了,这次深入探讨动态规划的第三篇,专门讨论单调优化在多重背包问题中的强大作用。

什么是单调优化?

单调优化是一种巧妙的技巧,它利用决策的单调性,大幅减少动态规划中的状态空间。简单来说,单调性是指如果一个决策在某个状态下是最佳的,那么在所有更优的状态下,它也一定是最佳的。

单调优化在多重背包问题中的应用

多重背包问题是一个经典的动态规划问题,任务是在容量有限的背包中,从一堆物品中挑选出价值最大的物品。

使用单调优化,我们可以将多重背包问题的朴素解法从 O(n * m) 优化到 O(n * m),其中 n 是物品数量,m 是背包容量。这是如何实现的?

首先,我们定义状态 dp[i][j],表示前 i 件物品放入容量为 j 的背包所能获得的最大价值。接着,我们枚举物品,如果第 i 件物品放入背包后价值更大,则更新 dp 值。

然而,朴素的解法存在问题:对于 j >= w[i],如果第 i 件物品不放入背包,后续决策也不可能让它放入。

单调队列优化

为了进一步提升效率,我们可以使用单调队列优化。单调队列是一种特殊的队列,它始终保持队列中的元素单调递增或递减。

在多重背包问题中,我们可以维护一个单调递减的队列,其中存储的是重量不小于 w[i] 的物品。如此一来,我们只需要考虑容量为 [0, j - w[i]] 的状态,大幅减少了状态转移的次数,将时间复杂度进一步优化到 O(n * m log n)。

代码示例

朴素解法:

for (int i = 1; i <= n; i++) {
  for (int j = 0; j <= m; j++) {
    dp[i][j] = dp[i - 1][j];
    if (j >= w[i]) {
      dp[i][j] = max(dp[i][j], dp[i - 1][j - w[i]] + v[i]);
    }
  }
}

单调队列优化:

deque<int> q;
for (int i = 1; i <= n; i++) {
  while (!q.empty() && w[q.back()] >= w[i]) {
    q.pop_back();
  }
  q.push_back(i);
  for (int j = 0; j <= m; j++) {
    dp[i][j] = dp[i - 1][j];
    if (j >= w[q.front()]) {
      dp[i][j] = max(dp[i][j], dp[i - 1][j - w[q.front()]] + v[q.front()]);
    }
  }
  while (!q.empty() && i - q.front() > w[i]) {
    q.pop_front();
  }
}

总结

单调优化和单调队列优化是动态规划中的两大杀手锏,它们通过利用决策的单调性,大幅减少了状态空间和计算时间。在多重背包问题中,单调优化可以将时间复杂度从 O(n * m) 优化到 O(n * m),而单调队列优化进一步将其优化到 O(n * m log n)。

掌握了单调优化和单调队列优化,你将所向披靡,在算法竞赛中笑傲江湖!

常见问题解答

  1. 单调优化的适用范围是什么?

    • 单调优化适用于决策具有单调性的动态规划问题,例如多重背包问题。
  2. 单调队列优化比单调优化快在哪里?

    • 单调队列优化利用单调队列的性质,减少了状态转移的次数,从而提升了效率。
  3. 如何判断一个决策是否具有单调性?

    • 如果一个决策在某个状态下是最佳的,那么在所有更优的状态下,它也一定是最佳的。
  4. 单调优化可以解决哪些类型的背包问题?

    • 单调优化可以解决多重背包问题和分数背包问题等。
  5. 单调队列优化有哪些应用场景?

    • 单调队列优化除了在背包问题中,还可用于最大子序和、最大连续乘积等问题中。