- 「数据结构详解·一」树的初步
- 「数据结构详解·二」二叉树的初步
- 「数据结构详解·三」栈
- 「数据结构详解·四」队列
- 「数据结构详解·五」链表
- 「数据结构详解·六」哈希表
- 「数据结构详解·七」并查集的初步
- 「数据结构详解·八」带权并查集 & 扩展域并查集
- 「数据结构详解·九」图的初步
- 「数据结构详解·十」双端队列 & 单调队列的初步
- 「数据结构详解·十一」单调栈
鸽了大半年,我回来了(
1. 单调栈的概念及实现
首先你要学过栈,没学过栈的可以看 「数据结构详解·三」栈。
单调栈(Monotone Stack),即元素满足单调性的栈。
我们直接以 P5788 【模板】单调栈 为例。
如果我们直接枚举每个数,那么显然时间复杂度是
O
(
n
2
)
O(n^2)
O(n2),并不能通过。
于是我们要想办法优化。
首先观察样例,1 4 2 3 5
。
我们会发现,对于
a
i
a_i
ai,我们只关心
a
i
+
1
∼
n
a_{i+1\sim n}
ai+1∼n 的数值的情况,前面都不用管。
所以我们可以开一个单调栈,其中的元素满足单调不增。这样,对于每个栈中的元素,在它头上都是比它小的,等到有比它大的元素出现的时候,它就会被弹出去,确定答案。
手动模拟一下:
- i = 1 i=1 i=1, 1 1 1 入栈;
- i = 2 i=2 i=2,此时发现 a 2 a_2 a2 大于栈顶的 a 1 a_1 a1,因此我们可以直接确定 f ( 1 ) = 2 f(1)=2 f(1)=2, 1 1 1 出栈, 2 2 2 入栈;
- i = 3 i=3 i=3, 3 3 3 入栈;
- i = 4 i=4 i=4,此时发现 a 4 a_4 a4 大于栈顶的 a 3 a_3 a3,因此我们可以直接确定 f ( 3 ) = 4 f(3)=4 f(3)=4, 3 3 3 出栈, 4 4 4 入栈;
- i = 5 i=5 i=5,此时发现 a 5 a_5 a5 大于栈顶的 a 4 a_4 a4,因此我们可以直接确定 f ( 4 ) = 5 f(4)=5 f(4)=5, 4 4 4 出栈;发现 a 5 a_5 a5 大于栈顶的 a 2 a_2 a2,因此我们可以直接确定 f ( 2 ) = 5 f(2)=5 f(2)=5, 2 2 2 出栈, 5 5 5 入栈。
于是最后答案就确定完毕了。
可以发现我们遍历的时间复杂度是
O
(
n
)
O(n)
O(n),而每个元素至多入栈一次、出栈一次,因此最终的时间复杂度是
O
(
n
)
O(n)
O(n),可以通过。
#include<bits/stdc++.h>
using namespace std;
stack<int>s;
int n,a[3000005],f[3000005];
int main()
{
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
while(!s.empty()&&a[s.top()]<a[i])//栈顶的元素遇到的第一个比它大的元素
{
f[s.top()]=i;//确定答案
s.pop();
}
s.push(i);
}
for(int i=1;i<=n;i++) cout<<f[i]<<' ';
return 0;
}
2. 例题详解
2-1. P1823 [COI2007] Patrik 音乐会的等待
我们考虑开一个单调递减的单调栈,这样栈内的一个人当遇到比自己高的人时就可以计数并弹出。
但是因为有身高相同的情况,那么我们只要栈内开一个 pair
,记身高与对应的人数即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
stack<pair<int,int>>s;
int n,ans;
signed main()
{
scanf("%d",&n);
while(n--)
{
int x;
scanf("%d",&x);
pair<int,int>p={x,1};
while(!s.empty()&&s.top().first<=x)
{
ans+=s.top().second;
if(s.top().first==x) p.second+=s.top().second;
s.pop();
}
if(!s.empty()) ans++;
s.push(p);
}
cout<<ans;
return 0;
}