单调栈
单调栈
什么是单调栈
顾名思义,如果栈中元素单调增(或减),则该栈被称为单调栈
如何维护单调栈
以维护一个单调增的栈为例, 插入元素2, 1, 4, 5, 3。
-
插入元素2,此时栈中为空,直接入栈;栈中元素:2。
-
插入元素1,栈顶元素2大于1,先出栈,此时栈中为空,插入1;栈中元素:1。
-
插入元素4,栈顶元素1小于4,插入4;栈中元素:1,4。
-
插入元素5,栈顶元素4小于5,插入5;栈中元素:1,4,5。
-
插入元素3,栈顶元素5大于3,先出栈,此时栈顶元素4大于3,再次出栈,最后栈顶元素1小于3,入栈,栈中元素:1,3。
这样我们就维护了一个单调增的栈了。
核心代码
stack<int>sta;
void insert(x){
while(!sta.empty() && sta.top() >= x)sta.pop();
sta.push(x);
}
应用
题目大意
给定一个数列\(\,a_{i...n}\),找到每一个数向右第一个大于它的数的下标,即\(\,a_i<a_j,i<j,\text {求每一个}\,a_i\text{对应的}\text {min}(j)\),没有则为0。\((1\le n \le 3\times10^6)\)
题目思路
如果我们直接暴力枚举,对每一个数\(\,a_i\,\)往右进行一次查询。这种做法的时间复杂度最坏的情况下是 \(O(n^2)\) 的,这无疑是会TLE的。下面考虑优化。
首先观察一个特殊情况:5,3,3,2,1。显然每个数都没有满足题意的下标,这时如果我们在其右边在插入一个大小为3的数,则可以模拟以下情况:
- 首先1和2小于3,则都有其对应答案。
- 接下来由于原来数列本身是不严格递减的,所以3及以后的数必定大于等于3,所以后面的数依旧没有答案。(这里的不严格递减指的是数列中\(\,a_i\le a_{i+1}, \forall\ i \in[1,n)\) 都成立。
- 因为1和2都有答案了,直接退出数列就好,这时,我们可以得到下一个数列:5,3,3,3。我们可以发现,得到的数列依然是一个不严格递减数列。重复上述步骤便可以得出每一个数答案了
经过上面的模拟过程,相信你应该知道为什么要用单调栈了,我们不妨维护一个不严格递减的单调栈,每次要出栈时记录答案即可,像上面的1和2一样。
这便是单调栈的一个简单应用
代码
#include<cstdio>
#include<stack>
int a[3000005]; //记录答案
int main(void){
int n;
std::stack<int>sta; //记录数据大小的栈
std::stack<int>ind; //记录对应下标的栈
scanf("%d",&n);
for(int i=1;i<=n;i++){
int key;
scanf("%d",&key);
while(!sta.empty()&&sta.top()<key){ //单调栈核心代码
a[ind.top()]=i; //记录大于该数的下标
sta.pop();
ind.pop();
}
sta.push(key);
ind.push(i);
}
for(int i=1;i<=n;i++)printf("%d ",a[i]);
return 0;
}

浙公网安备 33010602011771号