淺談單調棧/隊列

Preface

其實我是真的不想寫這個東西的,只不過作了一些這方面的水題,乾脆寫成一個專題git

真的是水題,不毒瘤的PJ難度水題。我真是太菜了算法


思想簡介

在維護一段區間的最值時,你通常會怎麼作?優化

\(O(1)\)查詢RMQ,或者是什麼都能搞的線段樹spa

若是隻須要求一次呢?code

仍是RMQ/線段樹隊列

若是數據範圍爲\(10^7\) or more?it

咱們就能夠用到玄學的單調棧/隊列了。io

首先相信棧和隊列你們都不陌生吧,那麼加了個單調上去意味着什麼呢?class

序列是單調的(廢話),說明咱們能夠很快的找出最值變量

好像有這麼一點道理,例如咱們要求出一段序列的最小值

咱們使一個隊列裏的元素單調遞減,具體的:

對於每個元素\(x\in q\),都有\(q_x>q_{x+1}\)

所以咱們直接把隊頭拿出來就行了啊。

那麼可能有人要問了,這TM不是直接開一個變量記錄一下最小值就好的事情嗎?

可是詢問區間你左端點仍是要向右彈出的啊!所以咱們都要保留一個對將來可能有用的解。

只有當一對\(x,y\)元素知足\(x>y\)\(a_x<a_y\)\(y\)的存在纔沒有任何有意義。

複雜度是比較卓越的\(O(n)\),通常起輔助做用,經常使用來優化DP等算法。

不過咱們今天不講這麼難的,咱們只講SB板子題


POJ 2823 Sliding Window

題目大意:求一段長度爲\(k\)的區間內的最小/大值。

這個單調隊列板子題。上面已經講的比較詳細了,咱們維護兩個單調隊列便可。

CODE

#include<cstdio>
#include<cctype>
const int N=1e6+5;
using namespace std;
int n,k,x;
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 write(int x)
{
    if (x<0) putchar('-'),x=-x;
    if (x>9) write(x/10); putchar(x%10+'0');
}
namespace min
{
    int q[N],num[N],ans[N],head=1,tail,cnt;
    inline void push(int x,int id)
    {
        while (tail>=head&&q[tail]>x) --tail;
        q[++tail]=x; num[tail]=id;
    }
    inline void check(int now)
    {
        if (num[head]+k<=now) ++head;
        ans[++cnt]=q[head];
    }
};
namespace max
{
    int q[N],num[N],ans[N],head=1,tail,cnt;
    inline void push(int x,int id)
    {
        while (tail>=head&&q[tail]<x) --tail;
        q[++tail]=x; num[tail]=id;
    }
    inline void check(int now)
    {
        if (num[head]+k<=now) ++head;
        ans[++cnt]=q[head];
    }
};
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    register int i; read(n); read(k);
    for (i=1;i<=k;++i)
    read(x),min::push(x,i),max::push(x,i);
    min::check(k); max::check(k);
    for (i=k+1;i<=n;++i)
    read(x),min::push(x,i),max::push(x,i),min::check(i),max::check(i);
    for (i=1;i<=min::cnt;++i)
    write(min::ans[i]),putchar(i^min::cnt?' ':'\n');
    for (i=1;i<=max::cnt;++i)
    write(max::ans[i]),putchar(i^max::cnt?' ':'\n');
    return 0;
}

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;
}
相關文章
相關標籤/搜索