【算法學習】單調隊列

基礎模板

求區間最大值最小值之差在[L,R]範圍內的最長區間長度c++

int l=1;
int l1=1,r1=0;
int l2=1,r2=0;
int ans=0;
for(int r=1;r<=n;r++){
    //刪除隊尾元素再入隊,使知足單調性
    while(l1<=r1 && a[r]>a[mxq[r1]]){
        r1--;
    }
    mxq[++r1]=r;
    while(l2<=r2 && a[r]<a[mnq[r2]]){
        r2--;
    }
    mnq[++r2]=r;
    //隊首元素只能用來維護max和min的最大差值
    while(l<=r && l1<=r1 && l2<=r2 && a[mxq[l1]]-a[mnq[l2]]>k){
        l++;
        //刪去隊頭無效元素
        while(l1<=r1 && mxq[l1]<l){
            l1++;
        }
        while(l2<=r2 && mnq[l2]<l){
            l2++;
        }
    }
    //根據最小差值的限制,更新答案
    if(l1<=r1 && l2<=r2 && a[mxq[l1]]-a[mnq[l2]]>=m){
        ans=max(ans,r-l+1);
    }
}

單調隊列經常使用做有大小限制,帶刪除操做的優先隊列?可用來優化dp,或其餘相似狀態轉移。數組

例題

HDU3530

即上面的模板題優化

HDU3706

題意

給定\(n\)\(A\)\(B\),生成一個序列\(S\),定義\(T_i\)爲以\(i\)結尾的長度爲\(A\)的一段區間的\(S_i\)最小值,求全部\(T_i\)的乘積。spa

分析

讀懂題意以後,這個模型就是區間長度固定的最小值,單調隊列維護便可。code

代碼

#include <bits/stdc++.h>
using namespace std;
const int N=1e7+10;
int n,A,B;
int q[N];
int p[N];
int main(void){
    // freopen("in.txt","r",stdin);
    while(~scanf("%d%d%d",&n,&A,&B)){
        int l=1;
        int l1=1,r1=0;
        int l2=1,r2=0;
        int ans=1;
        p[0]=1;
        for(int i=1;i<=n;i++){
            p[i]=1ll*p[i-1]*A%B;
        }
        for(int r=1;r<=n;r++){
            while(l1<=r1 && p[r]<p[q[r1]]){
                r1--;
            }
            q[++r1]=r;
            while(l<=r && l1<=r1 && r-q[l1]>(A+1)){
                l++;
                while(l1<=r1 && q[l1]<l){
                    l1++;
                }
            }
            ans=1ll*ans*p[q[l1]]%B;
        }
        printf("%d\n",ans);
    }
}

POJ2559

題意

\(n\)個高度,求最大面積的矩形。隊列

分析

經典模型之一,正反兩個方向用單調隊列預處理出每一個值做爲最小值能延伸到的位置,枚舉取面積最大值便可。it

代碼

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+50;
int n,a[N];
int q[N];
int le[N],ri[N];
int main(void){
    // freopen("in.txt","r",stdin);
    while(~scanf("%d",&n) && n){
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        int l=1;
        int l1=1,r1=0;
        for(int r=1;r<=n;r++){
            while(l1<=r1 && a[r]<a[q[r1]]){
                ri[q[r1]]=r-1;
                r1--;
            }
            q[++r1]=r;
            l++;
        }
        while(l1<=r1){
            ri[q[r1]]=n;
            r1--;
        }
        l=1;
        l1=1,r1=0;
        for(int r=n;r>=1;r--){
            while(l1<=r1 && a[r]<a[q[r1]]){
                le[q[r1]]=r+1;
                r1--;
            }
            q[++r1]=r;
            l++;
        }
        while(l1<=r1){
            le[q[r1]]=1;
            r1--;
        }
        ll ans=0;
        for(int i=1;i<=n;i++){
            ans=max(ans,1ll*(ri[i]-le[i]+1)*a[i]);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

CF91B

題意

給一個序列,詢問每一個數和最右邊一個比其小的數中間相間數的個數。io

分析

維護一個單調遞減隊列,只容許隊尾加入更小的元素,每次二分查找最遠的下標模板

  • 單調隊列一個新的用法,每次對一個元素不必定要入隊,像這個題,有用的是最遠的下標,因此若是這個數不是最小的(不能直接加入隊列),那就沒有必要加入,由於隊列中確定已經存在一個比其更小且更遠的元素。

代碼

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+50;
int n,a[N],ans[N];
//維護一個單調遞減隊列,只容許隊尾加入更小的元素,每次二分查找最遠的下標
int decQ[N];
int main(void){
    // freopen("in.txt","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    int l=n;
    int l1=1,r1=0;
    for(int r=n;r>=1;r--){
        bool ac=false;
        int t=0;
        if(l1>r1 || a[r]<=a[decQ[r1]]){
            decQ[++r1]=r;
            ans[r]=-1;
        }else{
            int ll=l1,rr=r1;
            int t=0;
            while(ll<=rr){
                int mid=(ll+rr)/2;
                if(a[decQ[mid]]<a[r]){
                    t=decQ[mid];
                    rr=mid-1;
                }else{
                    ll=mid+1;
                }
            }
            ans[r]=t-r-1;
        }
        
    }
    for(int i=1;i<=n;i++){
        printf("%d%c",ans[i],i==n?'\n':' ');
    }
    return 0;
}

CF251A

題意

給一個有序序列,求知足最大值減最小值之差小於等於d的三元組個數。class

分析

因爲序列保證有序,因此只須要用一個普通的序列相似尺取的方法維護當前這個數做爲最大值的貢獻。

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+50;
int n,d,a[N];
int q[N];
int main(void){
    // freopen("in.txt","r",stdin);
    scanf("%d%d",&n,&d);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    ll ans=0;
    int l=1,r=0;
    for(int i=1;i<=n;i++){
        //考慮每一個數加進去後的貢獻
        q[++r]=a[i];
        while(l<=r && a[i]-q[l]>d){
            l++;
        }
        int siz=r-l+1;
        ans+=1ll*(siz-1)*(siz-2)/2;
    }
    printf("%lld\n",ans);
    return 0;
}

HDU3415

題意

求環上的長度不超過\(k\)的最大連續和。

分析

連續子段和考慮前綴和來計算,枚舉每一個點做爲右端點,用單調隊列維護最多前\(k\)個數的前綴和最小值。

代碼

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
const int INF=0x3f3f3f3f;
int T,n,k,a[N],pre[N],q[N];
int main(void){
    // freopen("in.txt","r",stdin);
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&k);
        pre[0]=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            pre[i]=pre[i-1]+a[i];
        }
        for(int i=n+1;i<=n+n;i++){
            pre[i]=pre[i-1]+a[i-n];
            a[i]=a[i-n];
        }
        int l1=1,r1=0;
        int ans=-INF;
        int al=0,ar=0;
        for(int r=1;r<=n+k-1;r++){
            while(l1<=r1 && pre[r-1]<pre[q[r1]]){
                r1--;
            }
            q[++r1]=r-1;
            while(l1<=r1 && r-q[l1]>k){
                l1++;
            }
            if(l1<=r1){
                int tmp=pre[r]-pre[q[l1]];
                if(tmp>ans){
                    ans=tmp;
                    al=q[l1]+1;
                    ar=r;
                }
            }   
        }
        printf("%d %d %d\n",ans,(al-1)%n+1,(ar-1)%n+1);
    }
    return 0;
}

POJ2823

題意

求固定長度\(k\)的區間最大值和最小值。

分析

也是經典模型之一,單調隊列維護,去除過老的元素。

代碼

#include <cstdio>
#include <algorithm>
#include <cctype>
using namespace std;
const int N=1e6+50;
int n,k,a[N];
int q[N];
int mx[N],mn[N];
int main(void){
    // freopen("in.txt","r",stdin);
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    int l=1;
    int l1=1,r1=0;
    for(int r=1;r<=n;r++){
        while(l1<=r1 && a[r]>a[q[r1]]){
            r1--;
        }
        q[++r1]=r;
        while(l<=r && l1<=r1 && r-q[l1]+1>k){
            l++;
            while(l1<=r1 && r-q[l1]+1>k){
                l1++;
            }
        }
        mx[r]=a[q[l1]];
    }
    l=1;
    l1=1,r1=0;
    for(int r=1;r<=n;r++){
        while(l1<=r1 && a[r]<a[q[r1]]){
            r1--;
        }
        q[++r1]=r;
        while(l<=r && l1<=r1 && r-q[l1]+1>k){
            l++;
            while(l1<=r1 && r-q[l1]+1>k){
                l1++;
            }
        }
        mn[r]=a[q[l1]];
    }
    for(int i=k;i<=n;i++){
        printf("%d%c",mn[i],i==n?'\n':' ');
    }
    for(int i=k;i<=n;i++){
        printf("%d%c",mx[i],i==n?'\n':' ');
    }
    return 0;
}

19牛客多校第三場F

題意

給一個矩陣,求出最大的子矩陣知足最大值和最小值之差不超過\(m\)

分析

數據範圍給的是\(n^3\)的作法,對於二維的一個子矩陣,須要\(n^2\)的時間來枚舉上下底,而後使用單調隊列維護橫向的最大值與最小值之差。

關鍵一點在於枚舉上下底\(i\)行和\(j\)行後,中間的二維矩陣的每一列其實就能夠用這一列的最大值和最小值來表示,其餘數值都已經可有可無了,從而轉化爲一維數組的狀況。

代碼

#include <bits/stdc++.h>
using namespace std;
const int N=505;
const int INF=0x3f3f3f3f;
int T,n,m,a[N][N];
int mx[N],mn[N];
int mxq[N],mnq[N];
int main(void){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                scanf("%d",&a[i][j]);
            }
        }
        int ans=0;
        //枚舉列
        for(int i=1;i<=n;i++){
            for(int j=0;j<=n;j++){
                mx[j]=0;
                mn[j]=INF;
            }
            for(int j=i;j<=n;j++){
                int l=1;
                int l1=1,r1=0;
                int l2=1,r2=0;
                //枚舉右邊界,維護最小可行左邊界
                for(int r=1;r<=n;r++){
                    mx[r]=max(mx[r],a[r][j]);
                    mn[r]=min(mn[r],a[r][j]);
                    //維護兩個單調隊列mxq(最大值遞減),mnq(最小值遞增)
                    while(l1<=r1 && mx[r]>mx[mxq[r1]]){
                        r1--;
                    }
                    mxq[++r1]=r;
                    while(l2<=r2 && mn[r]<mn[mnq[r2]]){
                        r2--;
                    }
                    mnq[++r2]=r;
                    //維護兩個單調隊列使得max-min<=m
                    while(l<=r && l1<=r1 && l2<=r2 && mx[mxq[l1]]-mn[mnq[l2]]>m){
                        l++;
                        //刪去無效元素
                        while(l1<=r1 && mxq[l1]<l){
                            l1++;
                        }
                        while(l2<=r2 && mnq[l2]<l){
                            l2++;
                        }
                    }
                    ans=max(ans,(j-i+1)*(r-l+1));
                }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
相關文章
相關標籤/搜索