单调队列

  单调队列中的元素满足单调性,复杂度看起来像是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

原文地址:https://www.cnblogs.com/shzr/p/9069185.html