几匹单调队列水题
Posted shixinyi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了几匹单调队列水题相关的知识,希望对你有一定的参考价值。
本来想叫一坨的,但是突然想起一匹VK这个神奇的东西。
刷水题方法:
点题目,搜索单调队列,选中标签。
点击按照难度排序的图标。
从普及-开始做起。
我只选了几道做,像那些通过太少或目测不可做还有看不懂题的和以前做过的就不做了。
第一匹:滑动窗口
裸题。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long const int maxn=1e6+10; ll n,a[maxn],s,t,k,zz[maxn]; ll aa,ff;char cc; ll read() { aa=0;cc=getchar();ff=1; while(cc<‘0‘||cc>‘9‘) { if(cc==‘-‘) ff=-1; cc=getchar(); } while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar(); return aa*ff; } int main() { n=read();k=read(); for(int i=1;i<=n;++i) a[i]=read(); s=1,t=0; for(int i=1;i<=n;++i) { while(s<=t&&zz[s]<=i-k) s++; while(s<=t&&a[zz[t]]>=a[i]) t--; zz[++t]=i; if(i>=k) printf("%lld ",a[zz[s]]); } s=1,t=0; printf("\n"); for(int i=1;i<=n;++i) { while(s<=t&&zz[s]<=i-k) s++; while(s<=t&&a[zz[t]]<=a[i]) t--; zz[++t]=i; if(i>=k) printf("%lld ",a[zz[s]]); } return 0; }
第二匹:PIL-Pilots
写两个单调队列,当差值>k需要有出队的时候判断哪个位置靠前一些就出哪个,注意维护可行解的区间左端点。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=3e6+10; int k,n,a[maxn],zz1[maxn],zz2[maxn],s1,s2,t1,t2,ans; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<‘0‘||cc>‘9‘) cc=getchar(); while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar(); return aa; } int main() { k=read();n=read(); s1=s2=1;t1=t2=0; int tt=1; for(int i=1;i<=n;++i) { a[i]=read(); while(s1<=t1&&a[zz1[t1]]<=a[i]) t1--; while(s2<=t2&&a[zz2[t2]]>=a[i]) t2--; zz1[++t1]=zz2[++t2]=i; while(s1<=t1&&s2<=t2&&a[zz1[s1]]-a[zz2[s2]]>k) { if(zz1[s1]<zz2[s2]) tt=zz1[s1]+1,s1++; else tt=zz2[s2]+1,s2++; } ans=max(ans,i-tt+1); } printf("%d",ans); return 0; }
第三匹:良好的感觉
我们需要求以第i天为感受值最小的一天的最长区间,就需要找到感受值比它小的最靠近它的左右两个位置,直接两边单调队列。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long const int maxn=1e5+10; ll n,a[maxn],zz[maxn],t,l[maxn],ans,tot[maxn]; ll aa;char cc; ll read() { aa=0;cc=getchar(); while(cc<‘0‘||cc>‘9‘) cc=getchar(); while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar(); return aa; } int main() { n=read(); t=0; for(int i=1;i<=n;++i) a[i]=read(),tot[i]=tot[i-1]+a[i]; for(int i=1;i<=n;++i) { while(t&&a[zz[t]]>=a[i]) t--; if(t) l[i]=zz[t]; zz[++t]=i; } t=0; for(int i=n;i;--i) { while(t&&a[zz[t]]>=a[i]) t--; if(t) ans=max(ans,a[i]*(tot[zz[t]-1]-tot[l[i]])); else ans=max(ans,a[i]*(tot[n]-tot[l[i]])); zz[++t]=i; } printf("%lld",ans); return 0; }
第四匹:好消息,坏消息
区间值非负可以转化为前缀和的大小关系,然后他相当于把序列变成了一个环。那么我们把序列复制一份放在原序列后面,直接开始单调队列。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=1e6+10; int n,a[2*maxn],now,zz[maxn],s=1,t=0,ans; int aa,ff;char cc; int read() { aa=0;cc=getchar();ff=1; while(cc<‘0‘||cc>‘9‘) { if(cc==‘-‘) ff=-1; cc=getchar(); } while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar(); return aa*ff; } int main() { n=read(); for(int i=1;i<=n;++i) a[i]=a[i+n]=read(); for(int i=1;i<=2*n;++i) a[i]+=a[i-1]; for(int i=2*n;i;--i) { while(s<=t&&zz[s]>=i+n) s++; while(s<=t&&a[zz[t]]>=a[i]) t--; if(s>t&&i<=n) ans++; zz[++t]=i; } printf("%d",ans); return 0; }
我们需要满足可行区间内,对于第i天的温度最大值,区间里不能存在一个小于i的j使第j天的温度最小值>这个值。
然后把按照温度最小值放入单调队列,在当前温度最大值<单调队列队首的温度最小值就弹。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=1e6+10; int n,l[maxn],r[maxn],zz[maxn],s,t,ans; int aa,ff;char cc; int read() { aa=0;cc=getchar();ff=1; while(cc<‘0‘||cc>‘9‘) { if(cc==‘-‘) ff=-1; cc=getchar(); } while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar(); return aa*ff; } int main() { n=read();s=1,t=0;int now=0; for(int i=1;i<=n;++i) { l[i]=read();r[i]=read(); while(s<=t&&l[zz[s]]>r[i]) now=zz[s++]; ans=max(ans,i-now); while(s<=t&&l[zz[t]]<=l[i]) t--; zz[++t]=i; } printf("%d",ans); return 0; }
第六匹:[USACO13NOV]POGO的牛Pogo-Cow
很不懂给奶牛脚安弹簧是什么样的体验。
dp[i][j]表示当前在i点上一个点在j点的最大得分。
我们考虑用dp[j][k]更新dp[i][j],我们对于j开一个单调队列存k使dp[j][k]在单调队列里单调下降且k单调上升,然后直接二分。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=1000+10; int n,zz[maxn][maxn],t[maxn],dp[maxn][maxn],sum[maxn],ans; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<‘0‘||cc>‘9‘) cc=getchar(); while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar(); return aa; } struct Node{ int x,p; }node[maxn]; bool cmp(const Node& a,const Node& b) { return a.x<b.x; } int ef(int d,int x) { int l=1,r=t[d],mid,rs=0; while(l<=r) { mid=(l+r)>>1; if(node[zz[d][mid]].x>=x) rs=mid,r=mid-1; else l=mid+1; } return dp[d][zz[d][rs]]; } void get_ans() { sort(node+1,node+n+1,cmp); memset(t,0,sizeof(t)); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;++i) { for(int j=1;j<i;++j) { dp[i][j]=ef(j,2*node[j].x-node[i].x)+node[i].p; while(t[i]&&dp[i][zz[i][t[i]]]<=dp[i][j]) t[i]--; zz[i][++t[i]]=j; ans=max(ans,dp[i][j]); } dp[i][i]=node[i].p; while(t[i]&&dp[i][zz[i][t[i]]]<=dp[i][i]) t[i]--; zz[i][++t[i]]=i; } } int main() { n=read(); for(int i=1;i<=n;++i) { node[i].x=read();node[i].p=read(); ans=max(ans,node[i].p); } get_ans(); for(int i=1;i<=n;++i) node[i].x=1000000-node[i].x; get_ans(); printf("%d",ans); return 0; }
第七匹:WIL-Wilcze do?y
感觉做到后面越来越套路,有了前面6匹的铺垫,这匹就是水题。
给定一个长度为n的序列,你有一次机会选中一段连续的长度不超过d的区间,将里面所有数字全部修改为0。
请找到最长的一段连续区间,使得该区间内所有数字之和不超过p。
我们考虑,算出对于每个点i作为最长连续区间的右端点,然后它左方取最优的一段长度为d的区间,将里面所有数字全部修改为0。
如果把所有长度为d的区间的区间和求出来,然后我们考虑两个长度为d的区间[x-d+1,x]、[y-d+1,y]
如果x<y<=i且sum[x-d+1...x]<sum[y-d+1...y],那么把区间[x-d+1,x]数字全部修改为0一定不是最优的。
所以我们把这些长度为d的区间按照sum大小放入单调队列,找可行的sum最大的那个长度为d的区间再二分找前面还可以做贡献的最长的一份即可。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long const int maxn=2e6+10; ll n,p,d,zz[maxn],s=1,t,w[maxn],ans; ll aa;char cc; ll read() { aa=0;cc=getchar(); while(cc<‘0‘||cc>‘9‘) cc=getchar(); while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar(); return aa; } void add(ll i) { int y=w[i]-w[i-d]; while(s<=t&&w[i]-w[zz[s]]>p) s++; while(s<=t&&w[zz[t]]-w[zz[t]-d]<=y) t--; zz[++t]=i; } ll ef(ll x,ll i) { ll l=0,r=i,mid,rs=i; while(l<=r) { mid=(l+r)>>1; if(w[mid]>=x) rs=mid,r=mid-1; else l=mid+1; } return rs; } int main() { n=read();p=read();d=read(); if(d==n) printf("%lld",n); else { ans=d; for(ll i=1;i<=n;++i) { w[i]=read()+w[i-1]; if(i>=d) add(i),ans=max(ans,i-ef(w[i]-p-w[zz[s]]+w[zz[s]-d],zz[s]-d)); } } if(n>1e6) ans++; printf("%lld",ans); return 0; }
第八匹:理想的正方形
有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
啊,变成二维的而已啊,套路套路。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=1000+10; int n,m,l,a[maxn][maxn],num[2][maxn][maxn],zz[maxn],s,t,ans[2][maxn][maxn],totans=2e9; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<‘0‘||cc>‘9‘) cc=getchar(); while(cc>=‘0‘&&cc<=‘9‘) aa=aa*10+cc-‘0‘,cc=getchar(); return aa; } void doit(int p) { for(int i=1;i<=n;++i) { s=1;t=0; for(int j=1;j<=m;++j) { while(s<=t&&zz[s]<=j-l) ++s; if(!p) while(s<=t&&a[i][zz[t]]<=a[i][j]) --t; else while(s<=t&&a[i][zz[t]]>=a[i][j]) --t; zz[++t]=j; num[p][i][j]=a[i][zz[s]]; } } for(int j=l;j<=m;++j) { s=1,t=0; for(int i=1;i<=n;++i) { while(s<=t&&zz[s]<=i-l) ++s; if(!p) while(s<=t&&num[p][zz[t]][j]<=num[p][i][j]) --t; else while(s<=t&&num[p][zz[t]][j]>=num[p][i][j]) --t; zz[++t]=i; ans[p][i][j]=num[p][zz[s]][j]; } } } int main() { n=read();m=read();l=read(); for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) a[i][j]=read(); doit(0);doit(1); for(int i=l;i<=n;++i) for(int j=l;j<=m;++j) totans=min(totans,ans[0][i][j]-ans[1][i][j]); printf("%d",totans); return 0; }
以上是关于几匹单调队列水题的主要内容,如果未能解决你的问题,请参考以下文章