初识单调队列

P1886 滑动窗口

这道题是一道单调队列的基本练习,因为时间缘故,我就不再这里多说了,看到有一篇很好的题解在这里*转载*一下,讲得非常的详细,相信大家认真看一下,也是可以看懂的。

单调队列有两个性质

队列中的元素其对应在原来的列表中的顺序必须是单调递增的。

队列中元素的大小必须是单调递*(增/减/甚至是自定义也可以)

单调队列与普通队列不一样的地方就在于单调队列既可以从队首出队,也可以从队尾出队。

那么我们应该怎样实现单调队列呢?

就拿样例来谈谈,设以最小的为标准。

8 3
1 3 -1 -3 5 3 6 7

*下文中我们用q来表示单调队列,p来表示其所对应的在原列表里的序号。

1、由于此时队中没有一个元素,我们直接令1进队。此时,q={1},p={1}。

2、现在3面临着抉择。下面基于这样一个思想:假如把3放进去,如果后面2个数都比它大,那么3在其有生之年就有可能成为最小的。此时,q={1,3},p={1,2}

3、下面出现了-1。队尾元素3比-1大,那么意味着只要-1进队,那么3在其有生之年必定成为不了最小值,原因很明显:因为当下面3被框起来,那么-1也一定被框起来,所以3永远不能当最小值。所以,3从队尾出队。同理,1从队尾出队。最后-1进队,此时q={-1},p={3}

4、出现-3,同上面分析,-1>-3,-1从队尾出队,-3从队尾进队。q={-3},p={4}。

5、出现5,因为5>-3,同第二条分析,5在有生之年还是有希望的,所以5进队。此时,q={-3,5},p={4,5}

6、出现3。3先与队尾的5比较,3<5,按照第3条的分析,5从队尾出队。3再与-3比较,同第二条分析,3进队。此时,q={-3,3},p={4,6}

7、出现6。6与3比较,因为3<6,所以3不必出队。由于3以前元素都<3,所以不必再比较,6进队。因为-3此时已经在滑动窗口之外,所以-3从队首出队。此时,q={3,6},p={6,7}

8、出现7。队尾元素6小于7,7进队。此时,q={3,6,7},p={6,7,8}。

那么,我们对单调队列的基本操作已经分析完毕。因为单调队列中元素大小单调递*(增/减/自定义比较),因此,队首元素必定是最值。按题意输出即可。

下面放代码啦,为了方便大家复制(划掉),鄙人就写点注释,反正前面已经很清楚了。

    #include<cstdio>
    #include<cstring>
    using namespace std; 
    struct Monotone_queue
    {
        static const int maxn=1000001;
        int n,k,a[maxn];
        int q[maxn],head,tail,p[maxn];//同题目叙述一样,q是单调队列,p是对应编号。
        void read()
        {
            scanf("%d %d",&n,&k);
            for(int i=1;i<=n;++i)
                scanf("%d",&a[i]);}//读入不必说了
        void monotone_max()//单调最大值
        {
            head=1;
            tail=0;
            for(int i=1;i<=n;++i)
            {
                while(head<=tail&&q[tail]<=a[i])
                    tail--;
                q[++tail]=a[i];
                p[tail]=i;
                while(p[head]<=i-k)
                    head++;//☣
                if(i>=k)printf("%d ",q[head]);
            }
            printf("
");
        }
        void monotone_min()
        {
            head=1;
            tail=0;//为啥要这样呢?因为head要严格对应首元素,tail要严格对应尾元素,所以当tail>=head时,说明有元素。而一开始队列为空,说一要这样赋值。其实这跟普通队列一样。
            for(int i=1;i<=n;++i)
            {//a[i]表示当前要处理的值
                while(head<=tail&&q[tail]>=a[i])
                    tail--;//只要队列里有元素,并且尾元素比待处理值大,即表示尾元素已经不可能出场,所以出队。直到尾元素小于待处理值,满足"单调"。
                q[++tail]=a[i];//待处理值入队。
                p[tail]=i;//同时存下其编号
                while(p[head]<=i-k)
                    head++;//如果队首元素已经"过时",出队。
                if(i>=k)printf("%d ",q[head]);//输出最值,即队首元素。i>=k表示该输出,至于why就自己看题目。
            }
            printf("
");
        }
    }worker;
    int main()
    {
        worker.read();
        worker.monotone_min();
        worker.monotone_max();
        return 0;
    }
楼下是鄙人写的代码,有点丑,将就看吧!
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    int q[1000005],num[1000005],ch[1000005];
    int main()
    {
        int n,k;
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&ch[i]);
        }
        int head=0,tail=1;
        for(int i=1;i<=n;++i)
        {
            while(ch[i]<=q[tail]&&head<=tail)//
            //注意:head<=tail有等号因为我们可以使队列被从后弹出直到为空此时满足head=tail-1(eg:head=1,tail=0)(因为我们还没有进行插入操作) 
             tail--;
            tail++;
            q[tail]=ch[i];
            num[tail]=i;
            while(i-num[head]>=k)//注意有等号,因为要保证下一次插入时留一个空位! 
             head++;
            if(i>=k) printf("%d ",q[head]);
        }
        memset(num,0,sizeof(num));
        memset(q,0,sizeof(q));
        printf("
");
        head=0;tail=1;
        for(int i=1;i<=n;++i)
        {
            while(ch[i]>=q[tail]&&head<=tail)//
            //注意:head<=tail有等号因为我们可以使队列被从后弹出直到为空此时满足head=tail-1(eg:head=1,tail=0)(因为我们还没有进行插入操作) 
             tail--;
            tail++;
            q[tail]=ch[i];
            num[tail]=i;
            while(i-num[head]>=k)//注意有等号,因为要保证下一次插入时留一个空位! 
             head++;
            if(i>=k) printf("%d ",q[head]);
        }
        return 0;
    }
P.s:
注意代码中标识的注释,都是极易错的几个地方!!!
原文地址:https://www.cnblogs.com/mudrobot/p/13331049.html