zoukankan      html  css  js  c++  java
  • 单调队列

      单调队列中的元素满足单调性,复杂度看起来像是O(N^2),但是复杂度不能这么分析,每个数最多出入队各一次,所以应该是O(N)的。

      现在学习算法的思路是看懂后顺着做luogu标签。

      在luogu点单调队列的标签查到的题中,前三道竟然都不是单调队列。。。

      第一题(https://www.luogu.org/problemnew/show/P2952是个deque模板题;

      第二题(https://www.luogu.org/problemnew/show/P1747)是个搜索;

      第三题(https://www.luogu.org/problemnew/show/P1091)就更离谱了,是dp。

      现在进入正题:

      扫描:https://www.luogu.org/problemnew/show/P2032

      题意概述:固定长度区间的最大值:

       
    # include <cstdio>
    # include <iostream>
    # define R register int
    
    using namespace std;
    
    int x,f,n,k,h,t;
    int a[2000009];
    char c;
    
    struct S
    {
        int key,value;
    }q[2000009];
    
    int read() 
    {
        x=0,f=1;
        c=getchar();
        while (!isdigit(c)) {
            if (c=='-') f=-1;
            c=getchar();
        }
        while (isdigit(c)) 
        {
            x=(x<<3)+(x<<1)+(c^48);
            c=getchar();
        }
        return x*f;
    }
    
    void pushi(int x)
    {
        while (t>=h&&q[t].value<=a[x]) t--;
        q[++t].value=a[x];
        q[t].key=x;    
        while (q[h].key<=x-k) h++;
    }
    
    int main()
    {
        n=read();
        k=read();
        for (R i=1;i<=n;i++)
            a[i]=read();
        if(k==1)
        {
            for (R i=1;i<=n;i++) printf("%d
    ",a[i]);
            return 0;
        }
        for (R i=1;i<=n;i++)    
        {
            pushi(i);
            if(i>=k) printf("%d
    ",q[h].value);
        }
        return 0;
    }
    单调队列模板

         又出现了一些不和谐音符:

      逛画展:https://www.luogu.org/problemnew/show/P1638

      不知道能不能用单调队列,反正用尺取法就挺快,复杂度大概也是一样的。

       
    # include <cstdio>
    # include <iostream>
    # define R register int
    
    using namespace std;
    
    int ans=1000009,b,beg=1,en=1,x,m,n,S=0;
    int T[2005]={0};
    char c;
    int a[1000009]={0};
    
    int read()
    {
        x=0; c=getchar();
        while (!isdigit(c))
          c=getchar();
        while (isdigit(c))
        {
            x=(x<<3)+(x<<1)+(c^48);
            c=getchar();
        }
        return x;
    }
    
    int main()
    {
        n=read(); m=read();
        for (R i=1;i<=n;i++)
          a[i]=read();
        ans=n;
        b=1;
        while (1)
        {
            while (en<n&&S<m)
              if(T[a[en++]]++ ==0) S++;
            if(S<m) break;
            if(en-beg<ans) 
            {
                ans=en-beg;
                b=beg;
            }
            if(--T[a[beg++]]==0)   S--;
         }
         printf("%d %d",b,b+ans-1);
         return 0;
    }
    View Code

         求m区间内的最小值:https://www.luogu.org/problemnew/show/P1440

      emmmm,又是模板中的模板。

       
    # include <cstdio>
    # include <iostream>
    
    using namespace std;
    
    struct S
    {
        int key,value;
    };
    
    int a[2000009]={0};
    S Down[2000009]={0};
    int Dt,Dh,n,k;
    
    int readd() {
        int x = 0, f = 1;
        char c = getchar();
        while (!isdigit(c)) {
            if (c == '-') f = -1;
            c = getchar();
        }
        while (isdigit(c)) {
            x = (x << 3) + (x << 1) + (c ^ 48);
            c = getchar();
        }
        return x * f;
    }
    
    void push_down(int x)
    {
        while (Dt>=Dh&&a[x]<=Down[Dt].value) Dt--;
        Down[++Dt].value=a[x];
        Down[Dt].key=x;    
        while (Down[Dh].key<=x-k) Dh++;
    }
    
    int main()
    {
        scanf("%d%d",&n,&k);
        for (register int i=1;i<=n;i++)
          a[i]=readd();
        Dt=0; Dh=1;
        printf("0
    ");
        for (int i=2;i<=n;i++)
        {
            if(k==1) printf("%d
    ",a[i-1]);
            else
            {
                push_down(i-1);
                printf("%d
    ",Down[Dh].value);
            }
        }
    }
    View Code

        向右看齐:https://www.luogu.org/problemnew/show/P2947

      其实是个单调栈,但是不弹出队头的单调队列就成了单调栈,于是删了一行模板就过了。(主要还是懒得写栈

       
    # include <cstdio>
    # include <iostream>
    
    using namespace std;
    
    struct S
    {
        int value,key;
    };
    
    int n,a[100009];
    S q[100009];
    int ans[100009];
    int h,t;
    
    void add(int i)
    {
        while(t>=h&&q[t].value<=a[i]) t--;
        q[++t].value=a[i];
        q[t].key=i;
    }
    
    int main()
    {
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for (int i=n;i>=1;i--)
        {
            add(i);
            if(t==h) ans[i]=0;
            else ans[i]=q[t-1].key;
        }
        for (int i=1;i<=n;i++)
            printf("%d
    ",ans[i]);
        return 0;
    }
    View Code

      

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

      单调队列的第一道绿题!可喜可贺,然而点开一看。。。这不就是模板题*2嘛。

       
    # include <cstdio>
    # include <iostream>
    
    using namespace std;
    
    struct S
    {
        int key,value;
    };
    
    int n,k,mi,ma;
    int a[1000050]={0};
    S Up[1000050]={0},Down[1000050]={0};
    int Uh,Dh,Ut,Dt;
    
    
    int readd() {
        int x = 0, f = 1;
        char c = getchar();
        while (!isdigit(c)) {
            if (c == '-') f = -1;
            c = getchar();
        }
        while (isdigit(c)) {
            x = (x << 3) + (x << 1) + (c ^ 48);
            c = getchar();
        }
        return x * f;
    }
    
    void push_up(int x)
    {
        while (Ut>=Uh&&a[x]>=Up[Ut].value) Ut--;
        Up[++Ut].value=a[x];
        Up[Ut].key=x;    
        while (Up[Uh].key<=x-k) Uh++;
    }
    
    void push_down(int x)
    {
        while (Dt>=Dh&&a[x]<=Down[Dt].value) Dt--;
        Down[++Dt].value=a[x];
        Down[Dt].key=x;    
        while (Down[Dh].key<=x-k) Dh++;
    }
    
    int main()
    {
        scanf("%d%d",&n,&k);
        for (register int i=1;i<=n;i++)
          a[i]=readd();
        Uh=Dh=1;
        Ut=Dt=0;
        for (int i=1;i<=n;i++)
        {
            if(k==1) printf("%d ",a[i]);
            else
            {
                push_down(i);
                if(i>=k) printf("%d ",Down[Dh].value);
            }
        }
        cout<<endl;
        for (int i=1;i<=n;i++)
        {
            if(k==1) printf("%d ",a[i]);    
            else
            {
                push_up(i);
                if(i>=k) printf("%d ",Up[Uh].value);
            }
        }
        return 0;
    }
    View Code

       不能再刷水题了。。。明天开始做单调队列优化多重背包以及dp的题目吧。(2018-5-21)

      

      发射站:https://www.luogu.org/problemnew/show/P1901

      题意概述:每个发射站会向两侧分别发射能量,并让两边比它高且最近的塔接收到,求接受能量最大的那个塔接收了多少能量。

      单调队列板子题,正反各做一遍。

      
     1 # include <cstdio>
     2 # include <iostream>
     3 # include <cstring>
     4 # define R register int
     5 
     6 using namespace std;
     7 
     8 const int maxn=1000009;
     9 int h[maxn],v[maxn],a[maxn],n;
    10 int q[maxn],ans,Top=0;
    11 
    12 void ins (int x)
    13 {
    14     while (h[x]>h[ q[Top] ]&&Top)
    15     {
    16         a[x]+=v[ q[Top] ];
    17         Top--;
    18     }
    19     q[++Top]=x;
    20 }
    21 
    22 int main()
    23 {
    24     scanf("%d",&n);
    25     for (R i=1;i<=n;++i)
    26         scanf("%d%d",&h[i],&v[i]);
    27     for (R i=1;i<=n;++i)
    28         ins(i);
    29     Top=0;
    30     memset(q,0,sizeof(q));
    31     for (R i=n;i>=1;--i)
    32         ins(i);
    33     for (R i=1;i<=n;++i)
    34         ans=max(ans,a[i]);
    35     printf("%d",ans);
    36     return 0;
    37 }
    发射站

      单调队列优化dp:

      琪露诺:https://www.luogu.org/problemnew/show/P1725

      题意概述:从0到n走格子,格子上有分数,每次可以跳L~R步,求最大分数;

      首先想到一个dp方程:$dp[i]=maxegin{Bmatrix} dp[i-k]+a[i](l<=k<=r) end{Bmatrix} $

      因为知道这道题要单调队列,想了很久也没有想到这个题和单调队列有什么关系,后来查了一下才发现单调队列是用来优化dp的,而不是把整道题改成单调队列题。对于相邻的i,dp[i-k]的最大值很可能不变,如果变也只是在找最大值的范围内删一个数,减一个数,如果每次都用O(N)的时间查找会很慢,考虑用单调队列来优化一下。

      
    # include <cstdio>
    # include <iostream>
    # include <cstring>
    # include <algorithm>
    # define R register int
    
    using namespace std;
    
    struct edge
    {
        int key;
        long long value;
    }q[200009];
    
    int n,l,r,h=1,t=0;
    int rx,rf,a[200009];
    char rc;
    long long dp[200009],ans=0;
    
    void pushin(int x)
    {
        while (dp[x]>=q[t].value&&t>=h) t--;
        q[++t].value=dp[x];
        q[t].key=x;
        while (q[h].key<x+l-r&&h<=t) h++;
    }
    
    int read()
    {
        rx=0,rf=1;
        rc=getchar();
        while (!isdigit(rc))
        {
            if(rc=='-') rf=-rf;
            rc=getchar();
        }
        while (isdigit(rc))
        {
            rx=(rx<<3)+(rx<<1)+(rc^48);
            rc=getchar();
        }
        return rx*rf;
    }
    int main()
    {
        n=read(),l=read(),r=read();
        for (R i=0;i<=n;i++)
            a[i]=read();
        int tot=0;
        for (R i=l;i<=n;i++)
        {
            pushin(tot);
            dp[i]=max(dp[i],q[h].value+a[i]);
            tot++;
        }
        for (R i=n-r+1;i<=n;i++)
            ans=max(ans,dp[i]);
        printf("%lld",ans);
        return 0;
    }
    琪露诺

      

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

      题意概述:在n个连续的块中选出m个连续的块,使得这m块的价值和最大。

      同样想到一个朴素的dp: $dp[i]=maxegin{Bmatrix} s[i]-s[j-1](i-j+1<=m) end{Bmatrix} $

      循环i,则s[i]不变,变化的只有s[j-1],可以用单调队列维护。 

       
    // luogu-judger-enable-o2
    # include <cstdio>
    # include <iostream>
    # define LL long long
    # define R register int
    
    using namespace std;
    
    struct Nod
    {
        int key,value;
    }q[500005];
    int rx,rf,n,m,h=1,t=0;
    LL ans=0;
    char rc;
    LL S[500005]={0};
    
    long long read()
    {
        rx=0,rf=1;
        rc=getchar();
        while (!isdigit(rc))
        {
            if(rc=='-') rf=-rf;
            rc=getchar();
        }
        while (isdigit(rc))
        {
            rx=(rx<<3)+(rx<<1)+(rc^48);
            rc=getchar();
        }
        return rx*rf;
    }
    
    void pushin(int x)
    {
        while (S[x]<=q[t].value&&t>=h) t--;
        q[++t].value=S[x];
        q[t].key=x;
        while (q[h].key<x-m) h++;
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for (R i=1;i<=n;i++)
        {
            S[i]=read();
            S[i]+=S[i-1];
        }
        for (R i=1;i<=n;i++)
        {
            pushin(i);
            ans=max(ans,S[i]-q[h].value);
        }
        printf("%lld",ans);
    }
    切蛋糕

       

        电话线:https://www.luogu.org/problemnew/show/P2885

       题意概述:给定一个序列,它的代价是相邻两个元素差的绝对值*一个给定的数,可以以h*h的代价把任意元素增加h,求最小的总代价。

      现在发现了做单调队列题目的一些套路,首先写出dp的方程,进行一些化简或者是展开,会发现某些项在某种意义上是不变的(循环i,j,有的项只与i有关),某些项是单调的或者满足什么规律,就可以用单调队列或者是单调栈优化啦。说到单调栈,其实不就是单调队列去掉队首指针嘛,以前觉得这个东西好高端。有一次做题发现用不到队首指针,想了一下这就是栈啦,wzx教导我们不要把这些算法分的这么清,会用就行,%%%。

      (偷偷说一句luogu上这道题数据非常水,朴素dp也可以A...

       //思路待补全 

    # include <cstdio>
    # include <iostream>
    # include <cstring>
    # define R register int
    # define inf 123456710
    
    using namespace std;
    
    int now=1,n,c,rx,m=0;
    char rc;
    int a[100001];
    int minn,dp[2][101];
    
    inline char gc()
    {
        static char buff[1000000],*S=buff,*T=buff;
        return S==T&&(T=(S=buff)+fread(buff,1,1000000,stdin),S==T)?EOF:*S++;
    }
    
    int read()
    {
        rx=0;
        rc=gc();
        while (!isdigit(rc))
            rc=gc();
        while (isdigit(rc))
        {
            rx=(rx<<3)+(rx<<1)+(rc^48);
            rc=gc();
        }
        return rx;
    }
    
    int ab(int a)
    {
        if(a<0) return -a;
        return a;
    }
    
    int main()
    {
        n=read();
        c=read();
        memset(dp,1,sizeof(dp));
        for (R i=1;i<=n;i++)
            a[i]=read(),m=max(m,a[i]);
        dp[now][a[1]]=0;
        for (R i=a[1]+1;i<=m;i++)
            dp[now][i]=(i-a[1])*(i-a[1]);
        for (R i=2;i<=n;i++)
        {
            now=now^1;
            minn=inf;
            for (R j=a[i-1];j<=m;j++)
            {
                minn=min(minn,dp[now^1][j]-j*c);
                if(j>=a[i]) dp[now][j]=(j-a[i])*(j-a[i])+j*c+minn;
            }
            minn=inf;
            for (R j=m;j>=a[i];j--)
            {
                minn=min(minn,dp[now^1][j]+j*c);
                dp[now][j]=min(dp[now][j],(j-a[i])*(j-a[i])-j*c+minn);
            }
            memset(dp[now^1],1,sizeof(dp[now^1]));
        }
        int ans=dp[now][0];
        for (R i=a[n];i<=m;i++)
            ans=min(ans,dp[now][i]);
        printf("%d",ans);
        return 0;
    }
    电话线

      股票交易:https://www.luogu.org/problemnew/show/P2569

       题意概述:在T天中买股票,每天有买价售价,最大购买量,最大卖出量,最大持有量,求最大收益。

      其实这道题是夏令营讲过的,但是当时我沉迷于给上台演讲的wzx拍照就没听懂,所以现在又不会了...

      首先这个题的普通状态转移方程并不难想,$dp[i][j]$表示第i天手中有j股股票的最大收益,注意最大收益有可能小于0;

      这一天显然可以闲着,所以$dp[i][j]=dp[i-1][j]$;

      也可以买一些新的股票:$dp[i][j]=dp[i-w-1][k]-(j-k)*ap[i] (j<=k<=min(maxp,j+as[i])$

      也可以卖掉一些股票:$dp[i][j]=dp[i-w-1][k]+(k-j)*bp[i] (max(0,j-bs[i])<=k<=j)$

      但是这样显然会超时啊,所以就用单调队列优化一下。正常做法是第二种转移从小到大,第三种从大到小。因为我没有想到这样做,于是都从小到大转移,这样就要注意一些问题:每次放进队列的只是(上一次没放进来的最大值),但是对于某些奇怪的状态就会发现最小的j带来的状态已经不是(真正的最小状态)了,所以之前先把这样的状态放进去,到了不能再插入新状态的时候也可能会有状态过期,所以即使不能插入也必须扔掉过期状态。emmm倒着转移就可以避免这一些问题啦。

      
     1 # include <cstdio>
     2 # include <iostream>
     3 # include <cstring>
     4 # define R register int
     5 
     6 const int maxt=2005;
     7 int t,maxp,w,y;
     8 int ap[maxt],bp[maxt],as[maxt],bs[maxt];
     9 int dp[maxt][maxt];
    10 int q1[maxt],q2[maxt],h1,t1,h2,t2;
    11 int ans=0;
    12 
    13 void ins1 (int i,int j)
    14 {
    15     int las=std::max(0,j-as[i]);
    16     while (q1[h1]<las&&h1<=t1) h1++;
    17     while (dp[i-w-1][ q1[t1] ]+ap[i]*q1[t1]<=dp[i-w-1][j]+ap[i]*j&&h1<=t1) t1--;
    18     q1[++t1]=j;
    19 }
    20 
    21 void ins2 (int i,int j)
    22 {
    23     while (q2[h2]<j&&h2<=t2) h2++;
    24     while (dp[i-w-1][ q2[t2] ]+q2[t2]*bp[i]<=dp[i-w-1][j+bs[i]]+(j+bs[i])*bp[i]&&h2<=t2) t2--;
    25     q2[++t2]=j+bs[i];    
    26 }
    27 
    28 int main()
    29 {
    30     scanf("%d%d%d",&t,&maxp,&w);
    31     std::memset(dp,128,sizeof(dp));
    32     dp[0][0]=0;
    33     for (R i=1;i<=t;++i)
    34         scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
    35     for (R i=1;i<=t;++i)
    36     {
    37         std::memset(q1,0,sizeof(q1));
    38         std::memset(q2,0,sizeof(q2));
    39         h1=t1=h2=t2=1;
    40         for (R j=0;j<=maxp;++j)
    41         {
    42             dp[i][j]=dp[i-1][j];
    43             if(j<=as[i]) dp[i][j]=std::max(dp[i][j],-j*ap[i]);
    44         }
    45         if(i-w-1<0) continue;
    46         for (R j=-bs[i];j<=-1;++j)
    47             if(j+bs[i]<=maxp) ins2(i,j);
    48         for (R j=0;j<=maxp;++j)
    49         {
    50             ins1(i,j);
    51             dp[i][j]=std::max(dp[i][j],dp[i-w-1][ q1[h1] ]+q1[h1]*ap[i]-ap[i]*j);
    52             if(bs[i]+j<=maxp) ins2(i,j);
    53             while (q2[h2]<j&&h2<=t2) h2++;
    54             if(q2[h2]>=j) dp[i][j]=std::max(dp[i][j],dp[i-w-1][ q2[h2] ]+q2[h2]*bp[i]-bp[i]*j);
    55         }
    56     }
    57     for (R i=0;i<=maxp;++i)
    58         ans=std::max(ans,dp[t][i]);
    59     printf("%d",ans);
    60     return 0;
    61 }
    股票交易

      ---shzr

  • 相关阅读:
    0-J2EE
    3-Spring
    linux部分常用命令
    linux配置bond
    免密登录和配置网卡
    配置网卡的子接口
    mysqldump备份
    python的数据结构
    mysql一主一从复制
    Python3 基本数据类型和类型转换
  • 原文地址:https://www.cnblogs.com/shzr/p/9069185.html
Copyright © 2011-2022 走看看