zoukankan      html  css  js  c++  java
  • 【説明する】单调队列,单调栈

    说起这个话题,应该很多人会有一种似有所悟,但又不敢确定的感觉。

    (我差不多就是那样)

    没错,这正是因为其中“单调”一词的存在。

    • 那么单调是什么?
    • 学过函数的人都知道单调函数或者函数的单调性吧
    • 其实直白一点说单调,就是一直增或一直减。
    •     eg:1,3,5,9就是一个单调增数列,数列中不存在后一个数比前一个数小的现象。

    那么同样,在这里谈到的话题也有类似特点。

    (一)单调队列

      其实就是一个符合单调性质的队列,但它同时具有单调的性质以及队列的性质。

    使用频率不算高,但却占有至关重要的地位。它的作用很简单,就是为了维护一组单调数据,让我们在运行的过程中能够快速寻求前k个或后k个中最大或最小的值。

    (二)单调栈

      就是一个符合单调性质的栈并且它具有单调的性质以及栈的性质。


    上面的两个在作用方面是相同的,差别仅是在编程过程中维护的数组的方式不同

    (单调队列有两个指针,分别代表头和尾;单调栈有一个指针,代表头指针)

           下面举个简单的栗子来解释单调队列及单调栈。

           eg:有一组数据:1,5,9,4,7,8,6,将他们依此输入。同时,在某一时刻会让你求出后n个数中的最大值。                 

           根据题意,我们可以得出这样一个结论:

        若后一个数大于前一个数,则结果必定不会是前一个数

        (比如现在输入了1,5,由于1<5,所以无论是后几个数中的最大值均不会为1)。

        因此,我们只需维护一个单调递减的数组便可快速求得所需值。

    其中数组变化如下:

    输入——1,数组——1;
    输入——5,由于5>1删去1添入5,数组——5;
    输入——9,由于9>5删去5添入9,数组——9;
    输入——4,由于4<9直接添入,数组——9,4;
    输入——7,由于7>4同时7<9因此删去4添入7,数组——9,7;
    输入——8,由于8>4同时8<9因此删去7添入8,数组——9,8;
    输入——6,由于6<8直接添入,数组——9,8,6。

    总的来说,它的本质就是当你在插入一个值时,应将在他之前存入的所有小于他的数值剔除,再将他存入数组中。

         基础就差不多就这样ok辣~


    良心推荐一些题:

    单调队列专题:

    1.P1440 求m区间内的最小值 直通

    思路:

      就是进行求解固定区间之内的最小值

    上代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    const int N = 1e6 + 5;
    int n,k,l,r;
    int a[N],q[N],p[N];
    
    int main() {
        scanf("%d%d",&n,&k);
        for(int i=1; i<=n; i++) scanf("%d",&a[i]);
        for(int i=1; i<=n; i++) {
            if(q[l]<=i-k) l++;
            while(l<=r && a[i]<=a[q[r]]) r--; //单调递增 
            q[++r]=i;
            p[i]=a[q[l]];
        }
        for(int i=k; i<=n; i++) printf("%d ",p[i]);
        printf("
    ");
        memset(p,0,sizeof(p));
        l=0,r=0;
        for(int i=1; i<=n; i++) {
            if(q[l]<=i-k) l++;
            while(l<=r && a[i]>=a[q[r]]) r--; //单调递减 
            q[++r]=i;
            p[i]=a[q[l]];
        }
        for(int i=k; i<=n; i++) printf("%d ",p[i]);
        return 0;
    }
    View Code

    2.luogu P1886 滑动窗口 直通

    思路:

      单调队列的裸题,练手题,必刷!

      而且不仅有求最大值还有求最小值哦!

    坑点:

      看清楚输出的是什么!

    上代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    const int N = 1e6 + 5;
    int n,k,l,r;
    int a[N],q[N],p[N];
    
    int main() {
        scanf("%d%d",&n,&k);
        for(int i=1; i<=n; i++) scanf("%d",&a[i]);
        for(int i=1; i<=n; i++) {
            if(q[l]<=i-k) l++;
            while(l<=r && a[i]<=a[q[r]]) r--; //单调递增 
            q[++r]=i;
            p[i]=a[q[l]];
        }
        for(int i=k; i<=n; i++) printf("%d ",p[i]);
        printf("
    ");
        memset(p,0,sizeof(p));
        l=0,r=0;
        for(int i=1; i<=n; i++) {
            if(q[l]<=i-k) l++;
            while(l<=r && a[i]>=a[q[r]]) r--; //单调递减 
            q[++r]=i;
            p[i]=a[q[l]];
        }
        for(int i=k; i<=n; i++) printf("%d ",p[i]);
        return 0;
    }
    View Code

    3.luogu P1714 切蛋糕 直通

    思路:

      因为求的是幸运总和最大

      总和?

      当然就是求一下前缀和辣~

      所以我们首先求一个前缀和,然后维护一个单调递增的前缀和,最后更新答案时则使用max(ans,sum[i]-sum[q[l]])就结束辣!

    坑点:

      注意这里ans的初始化是什么,我认为应该初始化为-99999999什么的,不过初始化为0也是可以的!

      因为拥有ans=max(ans,sum[i]-sum[q[l]])这一步,所以如果当前的i==q[l]那么sum[i]-sum[q[l]]一定为0,所以无论你初始化ans多么小,ans最小都只能是0,所以需要把ans输出化为0.

    上代码:

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    using namespace std;
    
    const int N = 500005;
    int n,m,ans,l,r;
    int sum[N],q[N];
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; i++) {
            scanf("%d",&sum[i]);
            sum[i]+=sum[i-1];
            if(q[l]<i-m) l++;
            while(l<=r && sum[q[r]]>sum[i]) r--; //维护一个单调增区间 
            q[++r]=i;
            ans=max(ans,sum[i]-sum[q[l]]);
        }
        printf("%d",ans);
        return 0;
    }
    View Code

    4.P1638 逛画展 直通

    思路:

      我们需要用桶来维护一下单调队列,记录一下每个画家的作品出现的次数,如果队头的元素跟后面的元素有重复,那么就删除掉这个元素,以此类推

      然后最后的更新的话,必须保证所有颜色均出现过了,还需要记录一个last,表示上一次更新的区间的长度是多少。

      如果现在的区间长度比last要小,并且所有颜色均出现,那么就更新a,b端点坐标以及last,然后继续重复即可

    上代码:

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
    const int N = 1000001;
    const int M = 2001;
    int n,m,last=1<<30,a,b;
    int x[N],c[M];
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int r=1,l=1,now; r<=n; r++) {
            scanf("%d",&x[r]);
            if(c[x[r]]==0) m--;
            c[x[r]]++;
            while(l<r && c[x[l]]>1) {
                c[x[l]]--;
                l++;
            }
            now=r-l+1;
            if(m==0 && now<last) last=now,a=l,b=r;
        }
        printf("%d %d",a,b);
        return 0;
    }
    View Code

    额...至于单调栈什么的,我大概就做过一个题qwq,就光贴上地址吧

    直通

    ok,先暂时说这么多,以后我如果做到类似的题目还会发啦~大概

  • 相关阅读:
    欧拉筛,线性筛,洛谷P2158仪仗队
    树形DP和状压DP和背包DP
    洛谷P1144最短路计数题解
    洛谷P1373小a和uim大逃离题解
    LCA
    108. Convert Sorted Array to Binary Search Tree
    230. Kth Smallest Element in a BST
    94. Binary Tree Inorder Traversal
    144. Binary Tree Preorder Traversal
    236. Lowest Common Ancestor of a Binary Tree
  • 原文地址:https://www.cnblogs.com/zxqxwnngztxx/p/7762251.html
Copyright © 2011-2022 走看看