单调栈和单调队列学习/复习笔记
模板
-
P5788 【模板】单调栈
目的:
解决一个序列任意的一个数前/后第一个大于/小于他的数的问题。
实现:
- 首先先把输入的数组读进来
- 从后向前遍历,手写一个栈,对于扫到的每个元素都与栈顶元素进行比较。若扫到的元素大于等于栈顶元素(由于是手写栈,需要判断栈内是否还有元素,若没有直接加入元素即可),弹出栈顶元素。具体实现的话写一个
while
循环,不断比较进行r--
。 - 直到扫到的元素小于栈顶元素,此时的栈顶元素就是该扫到元素位置的答案,写一个数组记录下来(因为是倒序遍历,而输出要求正序输出)。
- 将扫到的元素加入该栈,注意加进去的为当前的数组下标,并非当前的元素。
- 最后正序遍历答案数组输出即可
#include<bits/stdc++.h> using namespace std; const int N=3e6+6; inline int read(); int n,a[N],ans[N],q[N]; int main() { n=read(); for(int i=1;i<=n;i++) { a[i]=read(); } int r=1; for(int i=n;i>=1;i--) { while(a[i]>=a[q[r]]&&r>0) { r--; } ans[i]=q[r]; q[++r]=i; } for(int i=1;i<=n;i++) { printf("%d ",ans[i]); } return 0; } inline int read() { int x=0,f=1; char ch; ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-') f=-f;ch=getchar();} while(ch<='9'&&ch>='0') { x=(x<<1)+(x<<3)+(ch&15); ch=getchar(); } return x*f; }
-
P1886 滑动窗口 /【模板】单调队列
目的:
解决需要得到当前的某个范围内的最小值或最大值的问题。
实现:
- 下面以求最小值为例
- 首先先把输入的数组读进来
- 从前向后遍历,手写一个双端队列,对于扫到的每个元素都与队尾元素进行比较,若扫到的元素小于等于队尾元素(由于是手写双端队列,需要判断队尾变量是否大于等于队首变量,即判断队列是否有元素),让队尾元素从队尾出去。
- 直到扫到的元素大于等于队尾元素,就让该元素从队尾进队。从程序的实现上来说,是让元素的序号进一个数组。
- 此时再看队首,要保证队首元素始终是改滑动窗口的答案,所以判断一下队首元素是否还位于改窗口中,若不在则从队首出队。程序具体的实现是
while(q[l]<=i-k) l++
这里的 \(q\) 数组存的是元素的位置编号。这个也是很好理解的,i-k+1
表示的是该窗口的最左端本应是什么,而q[l]
表示的是现在窗口最左端是什么,若现在的比本应的更靠左也就是小于等于,就应该l++
。 - 因为上一个操作保证了队首元素必为该滑动窗口的答案,所以只需判断一下这个滑动窗口是否完整就好了,代码实现就是
if(i>=k)
,如果成立输出队首元素即可。
#include<bits/stdc++.h> using namespace std; const int N=1e6+6; inline int read(); int n,k; long long a[N],q[N]; void min_q() { int r=0,l=1; for(int i=1;i<=n;i++) { while(r>=l&&a[q[r]]>=a[i]) { r--; } q[++r]=i; while(q[l]<=i-k) { l++; } if(i>=k) { printf("%lld ",a[q[l]]); } } puts(""); } void max_q() { int r=0,l=1; for(int i=1;i<=n;i++) { while(r>=l&&a[q[r]]<=a[i]) { r--; } q[++r]=i; while(q[l]<=i-k) { l++; } if(i>=k) { printf("%lld ",a[q[l]]); } } puts(""); } int main() { n=read();k=read(); for(int i=1;i<=n;i++) { a[i]=read(); } min_q(); max_q(); return 0; } inline int read() { int x=0,f=1; char ch; ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-') f=-f;ch=getchar();} while(ch<='9'&&ch>='0') { x=(x<<1)+(x<<3)+(ch&15); ch=getchar(); } return x*f; }
题
-
P2947 [USACO09MAR] Look Up S
几乎的模板题,注意一下看的方向和他要求输出的是编号而不是身高。(但是我一开始把 while
写成了 if
/fn/fn)
-
P2866 [USACO06NOV] Bad Hair Day S
考虑单调栈,一开始做的时候是想从后往前扫记录对于每个元素第一个大于该元素的下标,但不知道为何假了,始终只有 \(30\) 分(一开始也没开 long long
)。后来看了一小下题解,发现做法是从前往后扫,记录栈内的元素个数,这么一想感觉正解也挺好想的。
代码就几乎是一个单调栈模板,改成从前向后扫,注意一下统计答案后再将新元素放到栈中。
-
P1901 发射站
考虑单调栈,根据题目的意思先统一处理向右传递的,也就是从后向前扫一遍。扫描时维护元素右边第一个大于他的元素位置,第一个大于他的元素也就是栈顶元素加上该元素的能量值。接下来再统一处理向左传递的,也就是从前向后扫,其他同理。
代码也就是维护两个单调栈,一个是从后向前,另一个是从前向后。需要注意的时这个会爆 int
。
-
SP1805 HISTOGRA - Largest Rectangle in a Histogram
一道单调栈经典题,但我一开始做的时候上来直接看了看y总的视频题解,当看到在左右边各找边界时果断退出想要自己先想一想,但好像关键的思路这时候已经都听见了(。思路也还是挺好想的,对于每个单独的矩形,显然只有相连的矩形的高度大于或等于它时才能“继承”它的高度。所以做法也就显而易见了,维护一个元素左边和右边第一个小于它的元素位置,最后用右边的减去左边的乘上自己的高度即可。
代码也是维护两个单调栈,一个是左边第一个小于该元素的元素位置 \(le\) ,一个是右边第一个小于该元素的元素位置 \(ri\) 。最后的答案也就是对于每个元素计算出来的最大值,具体代码表现为 ans=max(ans,(ri[i]-le[i]-1)*a[i])
。最后注意一下这个会爆 int
,为什么这些题都要开 long long
啊/fn/fn/fn!!(最痛的是我一开始都没开)
-
P1823 [COI2007] Patrik 音乐会的等待
-
P2032 扫描
一道单调队列模板题,只是省去了求最小值,只求滑动窗口内的最大值。注意下输出时应输出队首元素,不要写顺手了写成 a[q[r]]
。
-
P1440 求m区间内的最小值
考虑单调队列,题目就是在原本滑动窗口的基础上去掉了必须构成完整的窗口的限制,也就是输出答案无需在判断滑动窗口是否完整。
代码注意一下题目求得时序列中 \(a_i\) 之前 \(m\) 个数的最小值,所以不包括 \(a_i\) 这个元素,所以循环只需要从 \(1\) 到 \(n-1\)。最后在求解前特别输出一个 \(0\) 作为 \(1\) 号元素前的元素的最小值就完成了。
-
P1714 切蛋糕
题意化简完就是求一段区间的最大子序和。可以发现想让其的子序和尽可能大,应让前缀和单调上升,所以考虑单调队列。由于他不是必须取一定多的元素,所以不需要判断是否完整,只需判断取的有没有超过限制的个数就行。这个时候样例就都过了,但是把代码交上去后发现 100
分但是 WA
。发现是最后一个点被 hack
了。lhl
大佬告诉我应该把 \(0\) 先加到单调队列里,要不然第一个元素就没有办法自己单独一个人了。
代码要实现先把 \(0\) 先加到单调队列可以把 \(r\) 的初值赋为 \(1\) ,这样队列里就提前有了一个 \(0\) 元素,另外因为用的前缀和 a[q[r]]-a[q[l]]
求解的,所以取的个数限制应该比原本大 \(1\) 个。最后在统计答案时就 ans=max(ans,a[q[r]]-a[q[l]])
,注意一下 \(ans\) 和前缀和都要开 long long
。
-
P1419 寻找段落
本文作者:一只小咕咕
本文链接:https://www.cnblogs.com/yzxgg/p/monotonous-stackqueue.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步