单调栈

定义:栈内元素单调按照递增(递减)顺序排列的栈

基本作用:可以从数组中找到左右两边比x大(小)的数,时间复杂度为O(n)

单调栈的基本操作:

●为了维护栈的单调性,在进栈过程中需要进行判断,具体进栈过程如下:假设当前进栈元素为e,

●对于单调递减栈,从栈顶开始遍历元素,把小于e或者等于e的元素弹出栈,直至遇见一个大于e的元素或者栈为空为止,然后再把e压入栈中,这样就能满足从栈底到栈顶的元素是递减的。

●对于单调递增栈,则每次弹出的是大于e或者等于e的元素,直至遇见一个小于e的元素或者栈为空为止。

                                     

java 单调栈算法 单调栈模板_java 单调栈算法

 

代码模板

 

a[n+1]=2e+9;
for (int i = 1; i <= n; i ++ )
{
    while (tt && a[s[tt]]>=a[i]) tt -- ;
    stk[ ++ tt] = i;
}

模板题

洛谷题库P5788[单调栈]

#include <iostream>
#include <cstdio>
#define INF 3000005
//头文件+预处理名。
using namespace std;
int n,a[Inf],q[INF],r,f[INF];
//定义变量,其中a数组表示输入的数,q数组表示存下标的单调栈,f数组是存结果。
int main() {
    scanf("%d",&n);
    for (int i=1; i<=n; i++) scanf("%d",&a[i]);
   //上面是读入。
    for (int i=n; i>=1; i--) {
    //从最后开始枚举。
        while (a[i]>=a[q[r]] && r>0) r--;
    //如果说它找到了比矮高的人并且不是最后,那么将那个矮的人弹掉,毕竟后面也不会有人看到它了,因为如果看到它的话那么必定可以看到比它前面的还比它高的。
        f[i]=q[r];
      //存结果,因为要正向输出。
        q[++r]=i;
     //将它入栈。
    }
    for (int i=1; i<=n; i++) printf("%d ",f[i]);
   //最后正着输出。
    return 0;
}

专项题

poj3494[求最大全1子矩阵]

#include <cstdio>
#include <cstring>
#include <algorithm>
 
using namespace std;
 
int n,m,h[100005],top,ans;
 
struct Node {
    int h,sta;//sta表示高度h的起始下标
}s[100005];
 
int main() {
    int hh,t;
    while(scanf("%d%d",&n,&m)==2) {
        memset(h,0,sizeof(h));
        ans=0;
        for(int i=1;i<=n;++i) {
            for(int j=1;j<=m;++j) {
                scanf("%d",&hh);
                h[j]=hh==0?0:h[j]+1;
            }
 
            t=m;
            h[++t]=-1;//令最后一个元素的下一个高度为-1,避免循环完毕后还要弹出栈中所有元素
            s[0].h=-1;
            s[0].sta=top=0;
            for(int k=1;k<=t;++k) {
                if(h[k]>=s[top].h) {
                    s[++top].h=h[k];
                    s[top].sta=k;//其起始下标就是自己的下标
                }
                else {
                    while(h[k]<s[top].h) {
                        ans=max(ans,(k-s[top].sta)*s[top].h);
                        --top;//弹出栈顶元素
                    }
                    s[++top].h=h[k];//其起始下标是弹出的最后一个元素的起始下标
                }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

 

 

单调队列

基本作用:解决滑动窗口的问题,这个问题是这样的:有一数列{an}和m个区间[L(i),R(i)],满足L(i)<=L(i+1),

R(i)<=R(i+1),对每个区间求区间最大值。时间复杂度O(n)

模板题

洛谷题库P1886[滑动窗口]

以求最小值为例观察队列中元素离开队列的情况:

1. 元素Vi从队尾离开队列:

i < j 且 Vi >= Vj,Vi没有参与求min的必要

2. 元素Vi从队首离开队列:

Vi超出了当前窗口的范围。

#include<cmath>
#include<queue>  
#include<cstdio> 
#include<cstring> 
#include<iostream> 
#include<algorithm>
using namespace std;
int a[1000001],n,wide,q[1000001],head,tail;
int main(){
    scanf("%d%d",&n,&wide);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]);
    head=1;tail=0;
    for(int i=1;i<=n;i++)
    {
     while(tail>0&&head<=tail&&i-q[head]>=wide)//对于超出窗口范围的元素,出队 
      head++;
     while(tail>0&&head<=tail&&a[q[tail]]>a[i])//对于比新元素更大的元素,出队 
      tail--;//通过这个操作能始终保持范围内最小的元素位于队首,且队列里的元素是单调递增的 
     q[++tail]=i;//入队 
     if(i>=wide) printf("%d ",a[q[head]]);//在元素已经足够多个时输出 
    }
    head=1;tail=0;
    memset(q,0,sizeof(q));
    printf("\n");
    for(int i=1;i<=n;i++)//同上寻找最小值的方法寻找最大值 
    {
     while(tail>0&&head<=tail&&i-q[head]>=wide)
      head++;
     while(tail>0&&head<=tail&&a[q[tail]]<a[i])
      tail--;
     q[++tail]=i;
     if(i>=wide) printf("%d ",a[q[head]]);
    }
    return 0;
}

经典题

 一本通1598[最大连续和]

●把前缀和当做值跑单调队列。

●求出前缀和sum[i],计算[l,r]区间和,就转化成了求sum[r]-sum[l-1]。

●枚举右端点i,则问题变为:找到一个左端点j,i−m<=j<=i−1且sum[j]最小。

●找到一个最小的前缀和(这样就可以保证这个区间内的和最大),用

sum[i]-这个最小前缀和sum[j]就是这个区间的最大值了,用ans来记录一下最大值即可。

#include <bits/stdc++.h>
using namespace std;
typedef int ll;
inline ll read()
{
    ll s=0;
    bool f=0;
    char ch=' ';
    while(!isdigit(ch))
    {
        f|=(ch=='-');
        ch=getchar();
    }
    while(isdigit(ch))
    {
        s=(s<<3)+(s<<1)+(ch^48);
        ch=getchar();
    }
    return (f)?(-s):(s);
}
#define R(x) x=read()
inline void write(ll x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x<10)
    {
        putchar(x+'0');
        return;
    }
    write(x/10);
    putchar((x%10)+'0');
    return;
}
inline void writeln(ll x)
{
    write(x);
    putchar('\n');
    return;
}
#define W(x) write(x),putchar(' ')
#define Wl(x) writeln(x)
const int N=200005,inf=0x3f3f3f3f;
int n,m,Qzh[N];
struct Record
{
    int Shuz,Weiz;
}Ddq[N];
int main()
{
//    freopen("sum12.in","r",stdin);
    int i,Head=0,Tail=0,ans=-inf;
    R(n); R(m);
    for(i=1;i<=n;i++)
    {
        int x=read(); Qzh[i]=Qzh[i-1]+x;
        while(Head<Tail&&Ddq[Head].Weiz<i-m) Head++;
        ans=max(ans,Qzh[i]-Ddq[Head].Shuz);
        while(Head<=Tail&&Qzh[i]<=Ddq[Tail].Shuz) Tail--;
        Ddq[++Tail]=(Record){Qzh[i],i};
    }
    Wl(ans);
    return 0;
}

 

大家可以自行再搜些题目练习