浅谈单调队列的应用

前言:单调队列,就是一个符合单调性质的队列,它同时具有单调的性质以及队列的性质。他在编程中使用频率不高,但却占有至关重要的地位。它的作用很简单,就是为了维护一组单调数据,让我们在运行的过程中能够快速寻求前k个或后k个中最大或最小的值。

例一(ssl 2521):
给出一个长度为n(1<=n<=100000)的整数序列,选择长度不超过k(1<=k<=n)的段,使得总和最大。

分析
方法一:枚举左端点l和右端点r,然后扫一遍计算总和并选取最大值。时间复杂度O(n^3)
方法二:预处理sum[i]表示前i个数的和,枚举左端点l和右端点r,然后O(1)计算总和并选取最大值。时间复杂度O(n^2)
方法三:仍然是预处理sum[i]表示前i个数的和,只枚举右端点r,问题就转换成了在sum[r-k..r-1]中找一个最小值。那么我们维护一个元素单调递增的队列,每次处理完一个r后把队尾所有不小于sum[r]的元素删掉后把sum[r]放进队尾。那么每次只用把队头中元素在原数组中的下标<r-k的元素删掉,然后选取队头即可得到以r为右端点的最大总和。

例二:
有n(1<=n<=100000)种物品,每种物品都有一个价值v[i]和费用w[i],还有一个数量s[i](1<=s[i]<=1000)。现在有m(1<=m<=1000)元,问最多能获得多少的价值。

分析:
大家:不会QAQ
敌敌:这不就是傻叉多重背包嘛,连我都会做,那还有人不会吗?
大家:……
其实这就是傻叉的多重背包,但会发现用最赤裸裸的方法是过不了的,连二进制优化也都不行。那下面就来介绍一下用单调队列来实现的时间复杂度为O(nm)的多重背包。
先回顾最赤裸裸的多重背包做法。
For (int i=1;i<=n;i++)
For (int j=m;j>=0;j--)
For (int k=1;k<=s[i]&&k*w[i]>=j;k++)
F[j]=min(f[j],f[j-k*w[i]]+v[i]*k);
很显然前两个循环是少不了的,那么我们来想如何去掉第三个循环,从而使时间复杂度变为我们想要的O(nm)。
容易得知对于任意的一个f[j](假设现在第一重循环为i)有可能更新到它的就只有f[j’]j’满足mod w[i]j同余且j’<j),f[j]有可能更新到的就只有f[j’]j’满足mod w[i]j同余且j’>j)
也就是说我们可以把j按照mod w[i]的余数将其分为不同的w[i]个部分,且每个部分都是相互独立的。
那么我们就可以采用以下的方法来对每一部分进行枚举:
for (j = 0; j < w[i]; j++)
    {
        for (k = j; k <= m; k += w[i])
        {
            // 状态转移
        }
    }
那么对于某一部分的一个状态假如是k,问题就转换成了在这一部分的数中找到一个l,满足l<k且(k-l)/w[i]<=s[i]且f[l]+v[i]*(k-l)/w[i]最大,这个东西我们显然可以用单调队列来维护。

练习题 poj 1742,ssl 2599,ssl 2570,hdu 3507
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值