单调队列

来源:互联网 发布:window10怎么安装软件 编辑:程序博客网 时间:2024/06/05 19:14

单调队列

今天问了长者有关单调队列的知识,单调队列这种东西其实用途并不是特别的广泛,只是在处理区间上询问的时候比较管用,而且这种询问有限制,比如:
一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。
这种类似的题目,每次询问前m个数中最小的,显然暴力的话是O(n * m)的每个点枚举一遍m,就是这样,但是暴力显然会TLE,那有什么办法呢?
RMQ是可以的,但是有一个问题,就是MLE,2000000 * 20,会炸空间,线段树显然是可以的,但是还不是最优的。所以我们引出单调队列这种东西,解决序列上滑块的问题(m这个区间在序列上动来动去,就像是个滑块^_^).

单调队列?

我们制作一个严格上升或下降的队列,它的队首是你处理当前区间的最大(最小)值,然后进行删除和加入的操作。
加入: 我们假设有8,1,2,3,7,9,6.这样的一个序列,我们想要一个单调上升的序列,这样就可以保证队首是最小的。我们加入元素的时候我们要存下每个值在原序列中的下标,因为,我们只有处理[i-m,i]这样的一个区间,如果队首元素的下标不在这个区间,显然就毫无意义了不。好,我们开始加入元素,我们每次添加元素,从队尾开始比较,直到找到比它小的数替换掉。
假设没有删除操作
第一次 :8
第二次:1(1 < 8 所以 我们替换掉8)
第三次:1,2.
第四次:1,2,3.
第五次:1,2,3,7.
第六次:1,2,3,7,9.
第七次:1,2,3,6.(跟7,9,比较都小,所以替换,尾指针直到6的位置)。

删除
删除其实很简单,我们比较队首的下标如果不在范围内直接删掉。

#include<iostream>#include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<algorithm>using namespace std;#define REP(i,a,b) for(register int i = (a), i##_end_ = (b); i <= i##_end_ ; ++i)inline int read(){    char c = getchar();register int fg = 1,sum = 0;    while(c < '0' || c > '9')    {        if(c == '-')fg = -1;        c = getchar();    }    while(c  >= '0' && c <= '9')    {        sum =sum * 10 + c - '0';        c = getchar();    }    return fg * sum;}const int maxn = 2000000+10;struct T{    int val,pos;};struct que{    int l,r;    T a[maxn];    que(){l = 1, r = 0;}    inline void putin(T x)//加入    {        while(r >= l && a[r].val > x.val)r--;        a[++r] = x;    }    inline void pop(int pos)//删除    {        while(r >= l && a[l].pos < pos)l++;    }    void print()    {        for(int i=l;i<=r;i++)            printf("%d ",a[i].val);        printf("\n");    }    inline int top()        {return a[l].val;}}q;int n,m,f[maxn];int main(){    n = read(),m = read();    REP(i,1,n)f[i] = read();    puts("0");    q.putin((T){f[1],1});    REP(i,2,n)    {   q.print();        if(i-m>=0)q.pop(i-m);        printf("%d\n",q.top());        q.putin((T){f[i],i});    }    return 0;}
0 0