前言:单调队列,就是一个符合单调性质的队列,它同时具有单调的性质以及队列的性质。他在编程中使用频率不高,但却占有至关重要的地位。它的作用很简单,就是为了维护一组单调数据,让我们在运行的过程中能够快速寻求前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
浅谈单调队列的应用
最新推荐文章于 2024-08-09 11:37:24 发布