浅谈单调栈/队列

Preface

其实我是真的不想写这个东西的,只不过做了一些这方面的水题,干脆写成一个专题

真的是水题,不毒瘤的PJ难度水题。我真是太菜了


思想简介

在维护一段区间的最值时,你一般会怎么做?

\(O(1)\)查询RMQ,或者是什么都能搞的线段树

如果只需要求一次呢?

还是RMQ/线段树

如果数据范围为\(10^7\) or more?

我们就可以用到玄学的单调栈/队列了。

首先相信栈和队列大家都不陌生吧,那么加了个单调上去意味着什么呢?

序列是单调的(废话),说明我们可以很快的找出最值

好像有这么一点道理,例如我们要求出一段序列的最小值

我们使一个队列里的元素单调递减,具体的:

对于每一个元素\(x\in q\),都有\(q_x>q_{x+1}\)

因此我们直接把队头拿出来就好了啊。

那么可能有人要问了,这TM不是直接开一个变量记录一下最小值就好的事情吗?

但是询问区间你左端点还是要向右弹出的啊!因此我们都要保留一个对未来可能有用的解。

只有当一对\(x,y\)元素满足\(x>y\)\(a_x<a_y\)\(y\)的存在才没有任何有意义。

复杂度是比较卓越的\(O(n)\),一般起辅助作用,常用来优化DP等算法。

不过我们今天不讲这么难的,我们只讲SB板子题


Luogu P2422 良好的感觉

比较简单的套路题,拿来练一下RMQ也是挺好的。

我们对于每一个点,处理出它两端大于它的第一个位置的数

这样我们就可以算出当\(i\in [l,r],a_i=min(l,r)\)时每一个数的贡献。

这样保证不会遗漏。由于\(a_i>0\)。因此取的数越多越好

CODE

#include<cstdio>
#include<cctype>
const int N=1e5+5;
using namespace std;
int n,k,x,a[N],stack[N],front[N],back[N],top,num[N];
long long sum[N],ans;
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; while (!isdigit(ch=tc()));
    while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline long long max(long long a,long long b)
{
    return a>b?a:b;
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    register int i; read(n);
    for (i=1;i<=n;++i)
    read(a[i]),sum[i]=sum[i-1]+a[i];
    for (i=1;i<=n;++i)
    {
        while (top&&stack[top]>=a[i]) --top;
        front[i]=num[top]; stack[++top]=a[i]; num[top]=i;
    }
    for (top=0,num[0]=n+1,i=n;i>=1;--i)
    {
        while (top&&stack[top]>=a[i]) --top;
        back[i]=num[top]; stack[++top]=a[i]; num[top]=i;
    }
    for (i=1;i<=n;++i)
    ans=max(ans,1LL*a[i]*(sum[back[i]-1]-sum[front[i]]));
    return printf("%lld",ans),0;
}

Luogu P2629 好消息,坏消息

又是一道SB题,我们先将序列展开,方便操作。

\(i\in[n+1,2n-1],a_i=a_{i-n}\),然后计算出前缀和

考虑每一次倒叙,就相当于求出一段长为\(n\)的区间的前缀和的最小值,并判断是否小于\(0\)

然后区间大小都固定了,上单调队列不是很爽吗?

CODE

#include<cstdio>
#include<cctype>
const int N=1e6+5;
using namespace std;
int n,k,x,a[N],q[N<<1],head=1,tail,num[N<<1],sum[N<<1],ans;
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
    while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag;
}
inline void push(int x,int id)
{
    while (tail>=head&&q[tail]>x) --tail;
    q[++tail]=x; num[tail]=id;
}
inline int check(int now)
{
    if (num[head]>now) ++head;
    return q[head];
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    register int i; read(n);
    for (i=1;i<=n;++i)
    read(a[i]),sum[i]=sum[i-1]+a[i];
    for (i=n+1;i<(n<<1);++i)
    sum[i]=sum[i-1]+a[i-n];
    for (i=(n<<1)-1;i>=n;--i)
    push(sum[i],i);
    for (i=n-1;i>=0;--i)
    {
        if (check(i+n)>=sum[i]) ++ans;
        push(sum[i],i);
    }
    return printf("%d",ans),0;
}

猜你喜欢

转载自www.cnblogs.com/cjjsb/p/9416168.html