1. 程式人生 > >單調隊列以及單調隊列優化DP

單調隊列以及單調隊列優化DP

圖片 區間最值 維護 前綴和 元素 void img hide stdout

單調隊列定義:

  其實單調隊列就是一種隊列內的元素有單調性的隊列,因為其單調性所以經常會被用來維護區間最值或者降低DP的維數已達到降維來減少空間及時間的目的。

  單調隊列的一般應用:

    1.維護區間最值

    2.優化DP

例題引入:

  https://www.luogu.org/problemnew/show/P1440

  一個含有n項的數列(n<=2000000),求出每一項前的m個數到它這個區間內的最小值。若前面的數不足m項則從第1個數開始,若前面沒有數則輸出0。

例題解答:

   首先看到題目可以很快想到O(NM),對於2*10^6這樣的數據無疑要TLE的;

   接下來考慮用單調隊列,因為每一個答案只與當前下標的前m個有關,所以可以用單調隊列維護前m的個最小值,

   考慮如何實現該維護的過程??

   顯然當前下標X的m個以前的元素(即下標小於X-M+1的元素)肯定對答案沒有貢獻,所以可以將其從單調隊列中刪除。

   對於兩個元素A,B,下標分別為a,b,如果有A>=B&&a<b那麽B留在隊列裏肯定優於A,因此可以將A刪除。

   維護隊首:如果隊首已經是當前元素的m個之前,將head++,彈出隊首元素

   維護隊尾:比較q[tail]與當前元素的大小,若當前元素更優tail++,彈出隊尾元素,直到可以滿足隊列單調性後加入當前元素。

   考慮單調隊列的時間復雜度:由於每一個元素只會進隊和出隊一次,所以為O(N)。

   一般建議用數組模擬單調隊列進行操作,而不用系統自帶的容器,因為系統自帶容器不易調試且可能有爆空間的危險。

代碼實現:

  

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define INF 0x3f3f3f3f
#define ll long long
#define maxn 2000009
#define maxm
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<0||ch>
9){if(ch==-) f=-1;ch=getchar();} while(ch>=0&&ch<=9){x=(x<<1)+(x<<3)+(ll)(ch-0);ch=getchar();} return x*f; } int n,m,k,tot,head,tail; int a[maxn],q[maxn]; int main() { // freopen(".in","r",stdin); // freopen(".out","w",stdout); n=read(),m=read(); for(int i=1;i<=n;i++) a[i]=read(); head=1,tail=0;//起始位置為1 因為插入是q[++tail]所以要初始化為0 for(int i=1;i<=n;i++)//每次隊首的元素就是當前的答案 { printf("%d\n",a[q[head]]); while(i-q[head]+1>m&&head<=tail)//維護隊首 head++; while(a[i]<a[q[tail]]&&head<=tail)//維護隊尾 tail--; q[++tail]=i; } // fclose(stdin); // fclose(stdout); return 0; }

習題報告:

  滑動窗口:https://www.luogu.org/problemnew/show/P1886

   解題思路: 此題與例題相同,只是所要求的是最大值和最小值,只需要做兩遍單調隊列即可  

技術分享圖片
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define maxn 1000009
#define maxm
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}
    while(ch>=0&&ch<=9){x=(x<<1)+(x<<3)+(ll)(ch-0);ch=getchar();}
    return x*f;
}
int q[maxn],a[maxn];
int n,m,k,ans,tot,head,tail; 

void Ask_MIN()
{
    head=1,tail=0;
    for(int i=1;i<=n;i++)
    {
        while(head<=tail&&i-q[head]+1>m)
            head++;
        while(head<=tail&&a[q[tail]]>=a[i])
            tail--;
        q[++tail]=i;
        if(i>=m)
            printf("%d ",a[q[head]]);    
    }
    puts("");
}

void Ask_MAX()
{
    head=1,tail=0;
    for(int i=1;i<=n;i++)
    {
        while(head<=tail&&i-q[head]+1>m)
            head++;
        while(head<=tail&&a[q[tail]]<=a[i])
            tail--;
        q[++tail]=i;
        if(i>=m)
            printf("%d ",a[q[head]]);
    }
    puts("");
}
int main()
{
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    Ask_MIN();
    Ask_MAX();
    fclose(stdin);
    fclose(stdout);
    return 0;
}
View Code

   擠奶牛:https://www.luogu.org/problemnew/show/P3088  

   解題思路:此題題目需要維護左和右分別D區間內的最大值,因此可以正著和倒著分別做一次單調隊列,然後打標記即可。

 

技術分享圖片
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define maxn 50009
#define maxm
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}
    while(ch>=0&&ch<=9){x=(x<<1)+(x<<3)+(ll)(ch-0);ch=getchar();}
    return x*f;
}
struct cow
{
    int h,x;
}p[maxn];
int q[maxn];
bool fear[maxn];
int n,m,k,ans,tot,head,tail; 
bool comp(cow a,cow b)
{
    return a.x<b.x;
}
int main()
{
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        p[i].x=read(),p[i].h=read();
    sort(p+1,p+1+n,comp);
    head=1,tail=0;
    for(int i=1;i<=n;i++)
    {
        while(head<=tail&&p[i].x-p[q[head]].x>m)
            head++;
        while(head<=tail&&p[i].h>=p[q[tail]].h)
            tail--;
        q[++tail]=i;
        if(p[q[head]].h>=2*p[i].h)
            fear[i]=1;
    }
    head=1,tail=0;
    for(int i=n;i>=1;i--)
    {
        while(head<=tail&&p[q[head]].x-p[i].x>m)
            head++;
        while(head<=tail&&p[q[tail]].h<=p[i].h)
            tail--;
        q[++tail]=i;
        if(p[q[head]].h>=p[i].h*2&&fear[i])
            ans++;
    }
    printf("%d\n",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}
View Code

  

單調隊列優化DP:

   切蛋糕:https://www.luogu.org/problemnew/show/P1714

   解題思路:同最大連續和,需要求出的是一個最大的長度為K的區間和。要快速知道一個區間的和可以用前綴和相減的形式來得到,那麽此題可以維護前綴和的單調性,保持

  

   

單調隊列以及單調隊列優化DP