多重背包の单调优化——算法中的闪电侠
2024-01-27 12:16:10
单调优化:多重背包问题的终极加速器
各位算法与数据结构的爱好者们,大家好!
我们又回来了,这次深入探讨动态规划的第三篇,专门讨论单调优化在多重背包问题中的强大作用。
什么是单调优化?
单调优化是一种巧妙的技巧,它利用决策的单调性,大幅减少动态规划中的状态空间。简单来说,单调性是指如果一个决策在某个状态下是最佳的,那么在所有更优的状态下,它也一定是最佳的。
单调优化在多重背包问题中的应用
多重背包问题是一个经典的动态规划问题,任务是在容量有限的背包中,从一堆物品中挑选出价值最大的物品。
使用单调优化,我们可以将多重背包问题的朴素解法从 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)。
掌握了单调优化和单调队列优化,你将所向披靡,在算法竞赛中笑傲江湖!
常见问题解答
-
单调优化的适用范围是什么?
- 单调优化适用于决策具有单调性的动态规划问题,例如多重背包问题。
-
单调队列优化比单调优化快在哪里?
- 单调队列优化利用单调队列的性质,减少了状态转移的次数,从而提升了效率。
-
如何判断一个决策是否具有单调性?
- 如果一个决策在某个状态下是最佳的,那么在所有更优的状态下,它也一定是最佳的。
-
单调优化可以解决哪些类型的背包问题?
- 单调优化可以解决多重背包问题和分数背包问题等。
-
单调队列优化有哪些应用场景?
- 单调队列优化除了在背包问题中,还可用于最大子序和、最大连续乘积等问题中。