单调队列
单调队列中的元素满足单调性,复杂度看起来像是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\n",a[i]); return 0; } for (R i=1;i<=n;i++) { pushi(i); if(i>=k) printf("%d\n",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; }
求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\n"); for (int i=2;i<=n;i++) { if(k==1) printf("%d\n",a[i-1]); else { push_down(i-1); printf("%d\n",Down[Dh].value); } } }
向右看齐: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\n",ans[i]); return 0; }
滑动窗口: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; }
不能再刷水题了。。。明天开始做单调队列优化多重背包以及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]=max\begin{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]=max\begin{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