单调队列优化
算法简析
单调队列是一种严格单调的队列,可以单调递增,也可以单调递减。队首位置保存的是最优解,第二个位置保存的是次优解。
单调队列可以有两个操作:
1、插入一个新的元素,该元素从队尾开始向队首进行搜索,找到合适的位置插入之,如果该位置原本有元素,则替换它。
2、在过程中从队首删除不符合当前要求的元素。
单调队列实现起来可简单,可复杂。简单的一个数组,一个head,一个tail指针就搞定。复杂的用双向链表实现。
算法使用场景
1、保存最优解,次优解,ect。
2、利用单调队列对dp方程进行优化,可将O(n)复杂度降至O(1)。也就是说,将原本会超时的N维dp降优化至N-1维,以求通过。这也是我想记录的重点
是不是任何DP都可以利用单调队列进行优化呢?答案是否定的。
记住!只有形如
dp[i]=(max/min)(f[k])+g[i] (k<i && g[i]是与k无关的变量)
才能用到单调队列进行优化。
优化的对象就是f[k]。
经典单调队列优化
题目
输入一个长度为n的整数序列,其中有正有负,数组中一个或连续的多个整数组成一个子数组,求所有子数组的和的最大值,要求时间复杂度为O(n)。
思路
转移方程:dp[i]=max(dp[i-1]+Data[i],Data[i]);
举例:
输入:{1,-2,3,10,-4,7,2,-5}
最大子数组为:{3,10,-4,7,2}=18;
步骤 | 操作 | 累加的子数组和 | 最大的子数组和 |
---|---|---|---|
1 | 加1 | 1 | 1 |
2 | 加-2 | -1 | 1 |
3 | 抛弃前面的和-1,加3 | 3 | 3 |
4 | 加10 | 13 | 13 |
5 | 加-4 | 9 | 13 |
6 | 加7 | 16 | 16 |
7 | 加2 | 18 | 18 |
8 | 加-5 | 13 | 18 |
示例代码
int FindGreatestSumOfSubArray(int *pData, int nLength)
{
if((pData == NULL) || (nLength <= 0))
{
g_InvalidInput = true;
return 0;
}
g_InvalidInput = false;
int nCurSum = 0;
int nGreatestSum = 0x80000000;//负无穷
for(int i = 0; i < nLength; ++i)
{
if(nCurSum <= 0)
nCurSum = pData[i];
else
nCurSum += pData[i];
if(nCurSum > nGreatestSum)
nGreatestSum = nCurSum;
}
return nGreatestSum;
}
总结
就是这样一种方式使得我们原本需要O(n^2^)直接降低成为了线性O(n)。
优化多重背包
多重背包一般解法
将物品的数量拆成0、1、2、4……等二的指数,分别计算其价值,当做单独的物品。因为用这些数可以组合出各种<=m(m为此种物品的数量)的数,也就可以用普通的01背包
状态转移方程
dp[i][j] = max{f[i - 1][j - k * we[i]] + k * va[i]} {0 <= k <= m[i]}
时间复杂度为Ο(nwlog(m))(n为物品种数,w为背包大小,m为此物品数量),是一种比较高效的方法。
==优化之后==
时间复杂度为Ο(n*w),只是常数可能会稍大。字母含义与上方相同
根据上面的式子,每个j值对应的k有m[i] + 1个里面去找最大的那个,就相当于在一个区间里面找一个最大值,可以考虑用单调队列来做这个事情,每次维护队列是单调递减的,每次取出来队列头作为转移,每次加入的时候,把前面的比它小的就出队,然后超过m[i]+1的也出队。
根据上面的式子,发现对于j这个维数来说,如果两个体积值对于we[i]取余的值不一样,是不可能转移的。这样的话直接按照mod的这个值来做转移,因为mod的值是在[0,we[i]-1],后面再枚举k的值。
假设mod = j % we[i],a = j / we[i],那么j = a * we[i] + mod,可得:
dp[i][j] = max{dp[i - 1][mod + (a - k) we[i]] + k va[i]} {0 <= k <= m[i]}
- 化简一下,把a - k 用k来替换就可以得到:
dp[i][j] = max{dp[i - 1][mod + k we[i]] - k va[i]} + a * va[i] {a - m[i] <= k <= a}
- 这样扫描的时候就是,第一重循环是枚举i为N(物品种数),第二重循环是枚举的mod从0到we[i]-1,然后第三重循环是枚举的余数为mod的情况下的k值,k值是从0到mod + k * we[i] <= V的对于每一个k对应一个这么大小的背包,然后找以这个为单调队列末尾的那个队列头的值,完成转移方程。