单调栈

单调栈

什么是单调栈

顾名思义,如果栈中元素单调增(或减),则该栈被称为单调栈

如何维护单调栈

以维护一个单调增的栈为例, 插入元素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);
}

应用

洛谷 P5788【模板】单调栈

题目大意

给定一个数列\(\,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;
}
posted @ 2022-05-24 21:38  言葉の庭  阅读(36)  评论(0)    收藏  举报