单调队列,单调栈相关

Posted 云深不知处

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单调队列,单调栈相关相关的知识,希望对你有一定的参考价值。

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

(我差不多就是那样)

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

  • 那么单调是什么?
  • 学过函数的人都知道单调函数或者函数的单调性吧
  • 其实直白一点说单调,就是一直增或一直减。
  •     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("\\n");
    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("\\n");
    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,先暂时说这么多,以后我如果做到类似的题目还会发啦~大概

以上是关于单调队列,单调栈相关的主要内容,如果未能解决你的问题,请参考以下文章

单调队列与单调栈用法详解

单调队列 单调栈

单调队列与单调栈作用

单调队列单调栈

HDU 6957 Maximal submatrix(悬线法 || 优先队列 || 单调栈 )

单调栈&单调队列