# 单调栈 --- ## 引入 何为单调栈?顾名思义,单调栈即满足单调性的栈结构。与单调队列相比,其只在一端进行进出。 为了描述方便,以下举例及伪代码以维护一个整数的单调递增栈为例。 --- ## 过程 ### 插入 将一个元素插入单调栈时,为了维护栈的单调性,需要在保证将该元素插入到栈顶后整个栈满足单调性的前提下弹出最少的元素。 例如,栈中自顶向下的元素为 $\{0,11,45,81\}$。 ![](http://39.99.183.126:8888/file/2/monotonous-stack-before.svg) --- 插入元素 $14$ 时为了保证单调性需要依次弹出元素 $0,11$,操作后栈变为 $\{14,45,81\}$。 ![](http://39.99.183.126:8888/file/2/monotonous-stack-after.svg) --- 用伪代码描述如下: ```text insert x while !sta.empty() && sta.top()
#include
#include
#include
#define maxn 1000100 using namespace std; int q[maxn], a[maxn]; int n, k; void getmin() { // 得到这个队列里的最小值,直接找到最后的就行了 int head = 0, tail = -1; for (int i = 1; i < k; i++) { while (head <= tail && a[q[tail]] >= a[i]) tail--; q[++tail] = i; } for (int i = k; i <= n; i++) { while (head <= tail && a[q[tail]] >= a[i]) tail--; q[++tail] = i; while (q[head] <= i - k) head++; printf("%d ", a[q[head]]); } } void getmax() { // 和上面同理 int head = 0, tail = -1; for (int i = 1; i < k; i++) { while (head <= tail && a[q[tail]] <= a[i]) tail--; q[++tail] = i; } for (int i = k; i <= n; i++) { while (head <= tail && a[q[tail]] <= a[i]) tail--; q[++tail] = i; while (q[head] <= i - k) head++; printf("%d ", a[q[head]]); } } int main() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); getmin(); printf("\n"); getmax(); printf("\n"); return 0; } ``` Ps. 此处的 "队列" 跟普通队列的一大不同就在于可以从队尾进行操作,STL 中有类似的数据结构 deque。 --- " 例题 2 [Luogu P2698 Flowerpot S](https://www.luogu.com.cn/problem/P2698)" 给出 $N$ 滴水的坐标,$y$ 表示水滴的高度,$x$ 表示它下落到 $x$ 轴的位置。每滴水以每秒 1 个单位长度的速度下落。你需要把花盆放在 $x$ 轴上的某个位置,使得从被花盆接着的第 1 滴水开始,到被花盆接着的最后 1 滴水结束,之间的时间差至少为 $D$。 我们认为,只要水滴落到 $x$ 轴上,与花盆的边沿对齐,就认为被接住。给出 $N$ 滴水的坐标和 $D$ 的大小,请算出最小的花盆的宽度 $W$。$1\leq N \leq 100000 , 1 \leq D \leq 1000000, 0 \leq x,y\leq 10^6$ --- 将所有水滴按照 $x$ 坐标排序之后,题意可以转化为求一个 $x$ 坐标差最小的区间使得这个区间内 $y$ 坐标的最大值和最小值之差至少为 $D$。我们发现这道题和上一道例题有相似之处,就是都与一个区间内的最大值最小值有关,但是这道题区间的大小不确定,而且区间大小本身还是我们要求的答案。 --- 我们依然可以使用一个递增,一个递减两个单调队列在 $R$ 不断后移时维护 $[L,R]$ 内的最大值和最小值,不过此时我们发现,如果 $L$ 固定,那么 $[L,R]$ 内的最大值只会越来越大,最小值只会越来越小,所以设 $f(R) = \max[L,R]-\min[L,R]$,则 $f(R)$ 是个关于 $R$ 的递增函数,故 $f(R)\geq D \implies f(r)\geq D,R\lt r \leq N$。这说明对于每个固定的 $L$,向右第一个满足条件的 $R$ 就是最优答案。 所以我们整体求解的过程就是,先固定 $L$,从前往后移动 $R$,使用两个单调队列维护 $[L,R]$ 的最值。当找到了第一个满足条件的 $R$,就更新答案并将 $L$ 也向后移动。随着 $L$ 向后移动,两个单调队列都需及时弹出队头。这样,直到 $R$ 移到最后,每个元素依然是各进出队列一次,保证了 $O(n)$ 的时间复杂度。 --- "参考代码" ```cpp #include
using namespace std; const int N = 100005; typedef long long ll; int mxq[N], mnq[N]; int D, ans, n, hx, rx, hn, rn; struct la { int x, y; bool operator<(const la &y) const { return x < y.x; } } a[N]; int main() { scanf("%d%d", &n, &D); for (int i = 1; i <= n; ++i) { scanf("%d%d", &a[i].x, &a[i].y); } sort(a + 1, a + n + 1); hx = hn = 1; ans = 2e9; int L = 1; for (int i = 1; i <= n; ++i) { while (hx <= rx && a[mxq[rx]].y < a[i].y) rx--; mxq[++rx] = i; while (hn <= rn && a[mnq[rn]].y > a[i].y) rn--; mnq[++rn] = i; while (L <= i && a[mxq[hx]].y - a[mnq[hn]].y >= D) { ans = min(ans, a[i].x - a[L].x); L++; while (hx <= rx && mxq[hx] < L) hx++; while (hn <= rn && mnq[hn] < L) hn++; } } if (ans < 2e9) printf("%d\n", ans); else puts("-1"); return 0; } ``` --- # 优先队列 一般用 `priority_queue` 或堆来实现。