单调队列
定义
有一个队列(双端队列), 队列中的元素满足下面四种状态的任何一种都为单调队列:
- 单调递增 (如 1 2 3 5 9)
- 单调不减 (如 1 2 3 3 5)
- 单调递减 (如 9 5 3 2 1)
- 单调不增 (如 5 3 3 2 1)
性质
(以单增队列为例)
插入时,需要将尾部大于此数的全部出队。
删除时,根据需要使队首或者队尾的元素出队(所以要用双端队列)。
每时每刻队首元素都代表着当前区间范围内的最小值。
单调队列维护元素值的同时也要维护元素的下标,以便知道每个元素和整个区间的关系。
实现
int q[maxn], p[maxn], ft, rr;
for(int i = 1; i <= n; ++i) {
while(rr > ft && q[rr-1] >= a[i]) --rr;
q[rr++] = a[i], p[rr-1] = i;
//if(p[ft]<k) ++ft;
}
应用
个人对单调队列有以下几点感觉:
单调队列多用于区间问题,而且大多数是区间长度或者其他的有限制的问题。
单调队列的研究对象一般是某个元素,而非某个区间(个人感觉), 个人感觉要维护的基本上是某个元素的一些相关属性。
有几个题目作为例子:
POJ - 2823 Sliding Window: 滑动窗口 单调队列
HDU - 3415 Max Sum of Max-K-sub-sequence : 单调队列
HDU - 3530 Subsequence : 单调队列
另外单调队列还有一个大用处就是优化一种形式的dp, 由于我还没有研究到dp,这里就先放一放,列一下知识点和一个题目感受一下。
(以下言论来自百度百科, 不可全信)
做动态规划时常常会见到形如这样的转移方程:
f[x] = max or min { g(k) | b[x] <= k < x } + w[x]
其中b[x]随x单调不降,即b[1]<=b[2]<=b[3]<=…<=b[n]
(g[k]表示一个和k或f[k]有关的函数,w[x]表示一个和x有关的函数)
这个方程怎样求解呢?我们注意到这样一个性质:如果存在两个数j, k,使得j <= k,而且g(k) <= g(j),则决策j是毫无用处的。因为根据b[x]单调的特性,如果j可以作为合法决策,那么k一定可以作为合法决策,并且k是一个比j要优的决策。因为k比j要优,(注意:在这个经典模型中,“优”是绝对的,是与当前正在计算的状态无关的),所以,如果把待决策表中的决策按照k排序的话,则g(k)必然是不降的。在此例中决策表即f[x].
这样,就引导我们使用一个单调队列来维护决策表。对于每一个状态f(x)来说,计算过程分为以下几步:
1、 队首元素出队,直到队首元素在给定的范围中。
2、 此时,队首元素就是状态f(x)的最优决策,
3、 计算g(x),并将其插入到单调队列的尾部,同时维持队列的单调性(不断地出队,直到队列单调为止)。
重复上述步骤直到所有的函数值均被计算出来。不难看出这样的算法均摊时间复杂度是O(1)的。因此求解f(x)的时间复杂度从O(n^2)降到了O(n)。
单调队列指一个队列中的所有的数符合单调性(单调增或单调减),在信息学竞赛的一些题目上应用,会减少时间复杂度.
可以参考下面这道题理解
单调栈
定义
个人感觉这个栈和队列是差不多的,其实单调栈只要在单调队列的基础上,关掉队列的一个端点不就行了, 只能在一端操作就是栈了,所以在实际的编码中单调栈和单调队列是差不多的(还是个人感觉)。
性质
同单调队列
实现
同单调队列
应用
目前我做到的四道题都只是用来求一个问题的:
就是在一个数组中, 求一个元素和它向左向右不比它小的元素所能组成的最大的区间,就是它最远能扩展到的地方,这应该是单调栈最简单的应用了(还没做到难的)。
另外还有三道题和这题差不多,我这里给出题目链接, 读者可作为练习实验下。
POJ - 3250 Bad Hair Day
POJ - 2796 Feel Good (这题是此书上的习题)
HDU - 1505 City Game