单调队列洛谷模板题
P1886 滑动窗口 /【模板】单调队列
1 //单调队列 2 //最小值为例: 3 //方框从左往右勾选 4 //一个数出现时,其左边元素已经被判断过是否为最小值 5 //若这个数比左边元素小,则其为更优解(数值小且不会先超出方框 6 //则其左边元素有生之年已经没有用了,直接删掉(既生瑜何生亮 7 //若这个数比左边元素大,则直接入队(数值大但后出框 8 //这个数可能在左边元素出框后成为最小值,因此入队候选(熬死上一任 9 //最后再删掉队中所有出框元素 10 //因此在此队列中,数组中的最小值会让其左边的数全部出队 11 //即队首元素始终为最小值,后方元素为候选项且队列严格递增 12 //求最小值输出队首元素即可(方框长度不够时需要跳过 13 //最大值同理,不多赘述 14 15 #include <bits/stdc++.h> 16 using namespace std; 17 int n,k; 18 const int maxn=1e6+5; 19 struct node{ 20 int w,vai; 21 }; 22 //w记录下标,方便判断是否超出窗口 23 //vai记录数值, 方便比较和输出 24 node minq[maxn],maxq[maxn]; 25 //最大值和最小值的单调队列 26 int minl,minr,maxl,maxr; 27 int a[maxn]; 28 int main(){ 29 scanf("%d%d",&n,&k); 30 maxl=minl=1; 31 minr=maxr=0; 32 //l严格指向队首,r严格指向队尾 33 //即l<=r时,队列不为空 34 for(int i=1;i<=n;i++){ 35 scanf("%d",&a[i]); 36 } 37 for(int i=1;i<=n;i++){ 38 //求最小值 39 while(minl<=minr&&minq[minr].vai>=a[i])minr--; 40 //队列不为空时,a[i]较小,为更优解 41 //a[i]之前的元素有生之年与a[i]同框无法作为最小值,去掉 42 //队尾元素在a[i]之前做过最小值或其候选项,现在已经没用了 43 minq[++minr].vai=a[i]; 44 minq[minr].w=i; 45 while(minq[minl].w<=i-k)minl++; 46 //如果队首元素(数组左边)超出方框则去掉 47 if(i>=k)printf("%d ",minq[minl].vai); 48 //特判,如果方框长度不够则不输出 49 //i指方框末端位置 50 } 51 printf("\n"); 52 for(int i=1;i<=n;i++){ 53 //求最大值 54 while(maxl<=maxr&&maxq[maxr].vai<=a[i]){ 55 maxr--; 56 } 57 maxq[++maxr].vai=a[i]; 58 maxq[maxr].w=i; 59 while(maxq[maxl].w<=i-k)maxl++; 60 if(i>=k)printf("%d ",maxq[maxl].vai); 61 } 62 printf("\n"); 63 return 0; 64 }
P2032 扫描
这题更弱,上面代码改成求最大值再改一下数据范围就能AC
P1714 切蛋糕
这题是单调队列加前缀和,long long开得没必要,可以换成int
1 //单调队列加前缀和 2 //求长度不大于m区间最大值就是求 3 //下标相差不大于m+1的前缀和的差的最大值 4 //求a[i]+...+a[i+m]=s[i+m]-s[i-1],相差m+1 5 //固定方框后方不懂,前方前缀和越小,差越大 6 //即求前缀和的单调上升队列 7 #include <bits/stdc++.h> 8 using namespace std; 9 int n,m; 10 const int maxn=500005; 11 int lucky[maxn]; 12 long long s[maxn]; 13 struct node{ 14 long long vai; 15 int w; 16 }que[maxn]; 17 int l=1,r; 18 long long ans=-0x7f7f7f7f; 19 int main(){ 20 scanf("%d%d",&n,&m); 21 for(int i=1;i<=n;i++){ 22 scanf("%d",&lucky[i]); 23 s[i]=s[i-1]+lucky[i]; 24 } 25 for(int i=1;i<=n;i++){ 26 while(l<=r&&que[l].w<i-m)l++; 27 //为什么加了等号是错的 28 //因为在前缀和中,a[i]到a[i+m]的区间和表示为s[i+m]-s[i-1] 29 //因此在出队时不能加等号,保留区间左端前一个数的前缀和 30 //所得得数才是此区间的和,不然就变成了 31 //区间【i+1~i+m】的和 32 ans=max(ans,s[i]-que[l].vai); 33 while(l<=r&&s[i]<=que[r].vai)r--; 34 que[++r].vai=s[i]; 35 que[r].w=i; 36 } 37 printf("%d",ans); 38 return 0; 39 }