单调队列优化

单调队列优化

算法简析

单调队列是一种严格单调的队列,可以单调递增,也可以单调递减。队首位置保存的是最优解,第二个位置保存的是次优解。

单调队列可以有两个操作:
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加111
2加-2-11
3抛弃前面的和-1,加333
4加101313
5加-4913
6加71616
7加21818
8加-51318

示例代码

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),只是常数可能会稍大。字母含义与上方相同

  1. 根据上面的式子,每个j值对应的k有m[i] + 1个里面去找最大的那个,就相当于在一个区间里面找一个最大值,可以考虑用单调队列来做这个事情,每次维护队列是单调递减的,每次取出来队列头作为转移,每次加入的时候,把前面的比它小的就出队,然后超过m[i]+1的也出队。

  2. 根据上面的式子,发现对于j这个维数来说,如果两个体积值对于we[i]取余的值不一样,是不可能转移的。这样的话直接按照mod的这个值来做转移,因为mod的值是在[0,we[i]-1],后面再枚举k的值。

  3. 假设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]}  
  1. 化简一下,把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} 
  1. 这样扫描的时候就是,第一重循环是枚举i为N(物品种数),第二重循环是枚举的mod从0到we[i]-1,然后第三重循环是枚举的余数为mod的情况下的k值,k值是从0到mod + k * we[i] <= V的对于每一个k对应一个这么大小的背包,然后找以这个为单调队列末尾的那个队列头的值,完成转移方程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值