文章

单调栈学习笔记

以洛谷 P5788 【模板】单调栈 为例。

题目描述 - 单调栈的基本作用

题目很简单:给定一个数列,求每一个数往后看第一个比它大的数是谁。

我们来看样例:

1
2
5
1 4 2 3 5

显而易见,对于第一个数字 1 ,第一个比它大的数字是 4 ,排在第二位;对于第二个数字 4 ,第一个比它大的数字是 5 ,排在第五位……以此类推。

深入理解单调栈

下面让我们把数据可视化。

首先迈出关键一步:因为题目中要求的是向右边看第一个比当前高的,也就是说已知信息应该在当前的右边。所以我们要以从右向左的顺序来寻找规律。否则,本题将难以解决。

我们先来看最后一个 5 。因为这是我们第一个看的,其右边没有更多信息了,所以按题目输出为 0

看完 5 ,往左一个看到 3 。由于 35 小,所以输出为 5 (这是真实 5 的位置)。同理, 2 的输出为 4 (这是真实 3 的位置)。

现在我们已经看了 2, 3, 5 ,也就是数字 4 的后面有 2, 3, 5。现在看到了数字 4 。我们发现 4 往后看一个是 2 ,然而 $4 > 2$ ,所以 2 不是我们要找的数,丢掉;丢掉了 2 ,看到 3 ,由于 $4 > 3$ ,所以 3 也不是要找的数,丢掉;终于看到了 5 ,由于 $4 < 5$ ,所以 5 就是我们要找的数了。

让我们来思考最后一个数字 1 。显然,它的对应数就是 4 。如果把 1 改成 7 呢?如图:

由于我们已经丢掉了 4 后面所有小于 4 的数字,所以这些被丢掉的数字一定也小于 7 ,我们不用操心这些数字里是否有我们可能要寻找的数字。那么,我们再次进行“丢掉”操作,现在 7 后面的数字有 4, 52, 3 已经被 4 丢掉了),而 4, 5 都小于 7 ,所以 4, 5 都被丢掉了,剩下了一个‘空’,所以 7 的答案变成了 0

让我们来重新审视一下整个过程:

  1. 从右往左看,如果后面的数字有比当前的数字小的,就丢掉;
  2. 丢到剩下面对的第一个比当前的大为止,这个数就是满足要求的。

这样一看,这似乎就是一个栈的操作,而且这个栈中的元素是单调递增或递减的。我们就将其称作单调栈

来看看 OI-wiki 上对单调栈的定义:

顾名思义,单调栈即满足单调性的栈结构。与单调队列相比,其只在一端进行进出。

代码实现

读入完后,我们需要:

  1. 从右往左看:
    1
    
    for(int i=n;i>=1;i--)
    
  2. 弹出栈中所有小于等于当前元素的:
    1
    2
    3
    
    while(!s.empty()&&a[i]>=a[s.top()]){
         s.pop();
    }
    
  3. 获得当前答案:
    1
    
    ans[i]=(s.empty()?0:s.top());
    
  4. 将当前元素压入栈中:
    1
    
    s.push(i);
    

注意:输入输出应该使用 scanf ,否则会超时。

Full Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <bits/stdc++.h>
using namespace std;
const int maxn=3e6+10;
int n;
int a[maxn];
int ans[maxn];
stack <int> s;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    for(int i=n;i>=1;i--){
        while(!s.empty()&&a[i]>=a[s.top()]){
            s.pop();
        }
        ans[i]=(s.empty()?0:s.top());
        s.push(i);
    }
    for(int i=1;i<=n;i++){
        printf("%d ",ans[i]);
    }
    return 0;
}
本文由作者按照 CC BY 4.0 进行授权

© Dignite. 保留部分权利。 由  提供CDN加速。

浙ICP备2023032699号 | 使用 Jekyll 主题 Chirpy