• 单调队列基础模板


    一天我在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
    ");
    	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
    ",now.num);
    	}return 0;
    } 
    

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

  • 相关阅读:
    Redis的特点
    JavaScript语言和JQuery技术
    学习javaDay13
    学习JavaDay12
    学习JavaDay11
    学习JavaDay10
    学习JavaDay09
    学习JavaDay08
    java的语法基础(二)
    java语法基础(一)
  • 原文地址:https://www.cnblogs.com/648-233/p/11156168.html
Copyright © 2020-2023  润新知