单调队列基础模板

一天我在luogu上看到这么一道题:

仔细思考之后...

恩,这不ST表吗!

然后一看数据范围:

(这图片是不是特别熟悉...)

听说有同学改了改数组大小,把ST板子往上一粘...

并且一看这两个点一定是无可挽回的,无论怎么优化,

所以说即使这个算法拿到了这个题的大部分分,这个算法还是不适合的

这说明什么?

luogu数据水

说明这题并不能用ST表过

那么有没有一种东西,它能够处理特定区间内的最值问题,还能跑的快(最好是O(n))呢?

下面引出今天的主角:单调队列!

在刚开始听的时候,觉得ta可能就是一个能够选取特定区间然后进行sort,最后还能处理出元素原来的入队顺序的STL?

其实并不是

首先ta并不是STL,一开始看到大佬打上\(deque\)时我还以为这就是单调队列,然而这只是双端队列

其次,单调队列指的单调并不是"单调函数"中的那个"单调",那个"单调"指的是"单调性",就是在指定的区间不上升或不下降

这里的单调指的是满足特定规则,

也就是说,这个队列中的元素既可以不上升,不下降,上升或下降,也可以在指定区间做像这样的运动:

只要ta符合一定的法则

具体的代码实现大纲就是在入队时写个函数,将不符合规则的元素做不影响结果的处理

而且这个东西还可以用于优化DP

以为大多DP需要枚举前面状态的若干结果,找最优解,如果用了单调队列,可以避免枚举步骤,直接状态转移

下面分析这道题

要求特定长度区间的最小值,那么这个特定长度可以看成是队列长度

然后分别将这个队列用来维护其元素的不下降,每次查询取队首就好辣

那么这个入队函数怎么写呢?

来模拟一遍:

每次遇到一个元素,将其入队时的操作考虑这样两种准则:

一个是保留最新的,就是当前入队元素,就是无论如何将当前新元素入队

另一个是保留对本题贡献最大的元素,要是一个元素老当益壮,就是即使老也仍是最大,就先不考虑退休问题,先保留

操作完是这样一种状态:

队首是最优元素,那么询问可以直接询问队首,当然需要先判断一步,就是这个队首元素是否已经要退休,是的话则出队,如果出现这种情况时并不会出现队空的情况,因为毕竟刚入一个"新秀"

除队首外的元素是一个不下降子序列,也就是后面的元素虽然大,但是等老元素退役时还是有较小的元素在队首

那么以后再询问的时候就可以将年轻的,较优的结果输出

总结一下,主要思想就是"退役思想",对于每个入队的元素,观察ta的值,如果ta比前一项优(并且比ta年轻,已保证,毕竟晚入队),那么排掉ta之前所有不如ta优的元素,因为前面的元素不够"优秀",就像某个奥赛团队来了一个新人,这个新人比你小并且比你强,那你就该退役了...

再见了同志们

或者说后面这个元素不够小,那么满足数列不下降这个需求,可以保留在队尾

经过以上步骤既可以把当前元素保留,又可以保证队首为最优,

如果同时要维护最大值和最小值(比如某个叫滑动窗口的题)

要么开两个队列,要么同一个队列两次用,先后处理最大值最小值

代码如下:

#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
int n,k;
struct node{
	int id,num;
};
int a[2000005];
deque<node> q;
inline void minn(const int &i,const int &v){
	while(!q.empty()){
		node now=q.back();
		if(now.num>v) q.pop_back();
		else break;
	}q.push_back((node){i,v});
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	printf("0\n");
	for(int i=2;i<=n;i++){
		minn(i-1,a[i-1]);
		node now=q.front();
		while(now.id<(i-k)){
			q.pop_front();
			now=q.front();
		}
		printf("%d\n",now.num);
	}return 0;
} 

再次说下,这里的deque是双端队列,front和back作用不言而喻..

posted @ 2019-07-09 11:22  _Alex_Mercer  阅读(415)  评论(1编辑  收藏  举报