【模板】单调栈

本题是一道单调栈的模板题。

首先说说什么是单调栈。

何为单调栈?顾名思义,单调栈即满足单调性的栈结构。——OI-Wiki

那什么是单调呢?

当函数 f(x) 的自变量在其定义区间内增大(或减小)时,函数值f(x)也随着增大(或减小),则称该函数为在该区间上具有单调性。——百度百科

其实很简单,只要序列rr满足以下条件,就可以被称为单调的:

  • i,0<i<r,ri<ri+1\forall i,0<i<|r|,r_i<r_{i+1}(单调递增)
  • i,0<i<r,riri+1\forall i,0<i<|r|,r_i\le r_{i+1}(单调不严格递增)
  • i,0<i<r,ri>ri+1\forall i,0<i<|r|,r_i>r_{i+1}(单调递减)
  • i,0<i<r,riri+1\forall i,0<i<|r|,r_i\ge r_{i+1}(单调不严格递减)

那什么是呢?

堆栈又名栈(stack),它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。——百度百科

现在把术语解释清楚了,下面我们就来康康单调栈有什么用。

一般来说,单调栈用来O(n)O(n)时间复杂度查找序列上每个元素左/右边第一个大于/小于/大于等于/小于等于它的元素。

在知道单调栈之前,我们写这样的程序是这样写的(以右边大于为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bits/extc++.h>
#define int long long
using namespace std;
using namespace __gnu_pbds;
int r[65536], n;
signed main()
{
scanf("%lld", &n);
for (int i{1}; i <= n; i++)
scanf("%lld", r + i);
for (int i{1}; i <= n; i++)
{
bool f{};
for (int j{i + 1}; j <= n; j++)
if (r[j] > r[i])
{
printf("%lld ", j);
f = true;
break;
}
if (!f)
printf("0 ");
}
return 0;
}

通过分析,我们可以得知,这段代码的时间复杂度为O(n2)O(n^2)(双重循环),显然会TLE,所以很多年以后,珂学家们发现单调栈可以对这段代码进行优化,我现在就拿1 1 4 5 1 4 1 9 1 9 8 1 0作为rr数组的值告诉大家如何用单调栈解决这个问题。

  1. 栈内没有元素,r1r_1进栈;

  2. 栈顶元素不小于r2r_2r2r_2进栈;

  3. 栈顶元素小于r3r_3r3r_3把栈顶元素挤出去,并成为它的答案;

  4. 栈顶元素还是小于r3r_3r3r_3把栈顶元素挤出去,并成为它的答案;

  5. 栈内没有元素,r3r_3进栈;

  6. 栈顶元素小于r4r_4r4r_4把栈顶元素挤出去,并成为它的答案;

  7. 栈内没有元素,r4r_4进栈;

  8. 栈顶元素不小于r5r_5r5r_5进栈;

  9. 栈顶元素小于r6r_6r6r_6把栈顶元素挤出去,并成为它的答案;

  10. 栈顶元素不小于r6r_6r6r_6进栈;

  11. 栈顶元素不小于r7r_7r7r_7进栈;

  12. 栈顶元素小于r8r_8r8r_8把栈顶元素挤出去,并成为它的答案;

  13. 栈顶元素还是小于r8r_8r8r_8把栈顶元素挤出去,并成为它的答案;

  14. 栈顶元素还是小于r8r_8r8r_8把栈顶元素挤出去,并成为它的答案;

  15. 栈内没有元素,r8r_8进栈;

  16. 栈顶元素不小于r9r_9r9r_9进栈;

  17. 栈顶元素小于r10r_{10}r10r_{10}把栈顶元素挤出去,并成为它的答案;

  18. 栈顶元素不小于r10r_{10}r10r_{10}进栈;

  19. 栈顶元素不小于r11r_{11}r11r_{11}进栈;

  20. 栈顶元素不小于r12r_{12}r12r_{12}进栈;

  21. 栈顶元素不小于r13r_{13}r13r_{13}进栈;

最后,栈内还剩下r8r_8r10r_{10}r11r_{11}r12r_{12}r13r_{13},它们没有答案。

至此,我们用O(n)O(n)的时间复杂度算出来了序列上每个元素右边第一个大于它的元素。左边、小于/大于等于/小于等于同理。这就是单调栈的用法。

最后,贴上交流电(AC)代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<bits/stdc++.h>
#define int long long
using namespace std;
int r[3000006],n,ans[3000006];
stack<int>s;
signed main()
{
cin>>n;
for(int i {1}; i<=n; i++)
cin>>r[i];
for(int i {1}; i<=n; i++)
{
while(!s.empty()&&r[s.top()]<r[i])
ans[s.top()]=i,s.pop();
s.push(i);
}
for(int i {1}; i<=n; i++)
cout<<ans[i]<<' ';
return 0;
}

谢谢大家的观看!