单调栈与单调队列复习
单调队列
解决问题
对于给定序列\(a\), 求固定长度区间\(k\), 关系运算最大或最小的问题.
最朴素的做法是\(O(nk)\), 利用单调队列可以将复杂度降为\(O(n)\).
思路
以求区间最大为例:
假设存在一个长度为\(k\)的队列,从\(1 \to n\)枚举,若\(a_i \ge a_j (a_j \in queue \and j \le i)\) , 那么\(a_j\)对答案的贡献变为0,此时我们可以将该元素移出队列。
所以做法可以简化维护一个严格单调递减的队列。
Code
#include <iostream>
#define rep(i, j, k) for(int i = j; i <= k; i ++)
const int N = 1e6 + 10;
int q[N], hh = 0, tt = -1;
int a[N];
int main() {
int n, k;
std::cin >> n >> k;
for(int i = 1; i <= n; i ++) {
std::cin >> a[i];
}
for(int i = 1; i <= n; i ++) {
// 每次检查队头是否不在窗口内
if(hh <= tt && q[hh] < i - k + 1) hh ++;
// 维护单调递减队列
while(hh <= tt && a[q[tt]] >= a[i]) tt --;
q[++ tt] = i;
if(i >= k)
std::cout << a[q[hh]] << " \n"[i == n];
}
hh = 0, tt = -1;
for(int i = 1; i <= n; i ++) {
if(hh <= tt && q[hh] < i - k + 1) hh ++;
while(hh <= tt && a[q[tt]] <= a[i]) tt --;
q[++ tt] = i;
if(i >= k)
std::cout << a[q[hh]] << " \n"[i == n];
}
}
单调栈
解决问题
对于给定序列\(a\), 求其与其最近的满足可比较关系的问题。
以求每个数\(a_i\)右侧的第一个小于它的数为例:
朴素做法:\(\Theta(n^2)\), 单调队列做法:\(O(nlogn)\), 单调栈做法\(O(n)\)
思路
假定求\(a_i\)右侧的第一个小于它的数,对于\(1 \le k_1, k_2 < i\) , 若\(k_1 < k_2, \space a_{k_1} \ge a_{k_2}\), 则\(a_{k_1}\) 对答案的贡献为0。
所以对于每个\(i\) ,我们可以维护一个严格单调递增的序列来保证对答案的贡献,由于每次只对序列尾端操作,所以我们可以用栈来解决该问题,并且由于单调递增, 并且对于每个数在维护中可以求得栈尾端元素即为答案。
Code
#include <iostream>
const int N = 1e5 + 10;
int a[N], stk[N], tt = 0;
int main() {
int n;
std::cin >> n;
for(int i = 1; i <= n; i ++) {
std::cin >> a[i];
while(tt && a[stk[tt]] >= a[i]) tt --;
if(tt) {
std::cout << a[stk[tt]];
} else {
std::cout << "-1";
}
std::cout << " \n"[i == n];
stk[++ tt] = i;
}
}