单调栈&单调队列

[刷题记录]单调栈&单调队列练习

  • 最近学到单调栈&单调队列,题目也开始有点考验思维了,下面做简单介绍。

单调栈

概念

  • 顾名思义,就是满足单调性结构,可以是单调递增or递减or自己定义。

实现(核心代码)

1
2
3
4
5
while(!stack.empty() && x > stack.top()){ // 严格单调递增
//这里一般做一些答案处理
stack.pop();
}
stack.push(x);

练习题

1. POJ 3250 Bad Hair Day

题意
  • 有$N$头牛从左到右排成一排,每头牛有一个高度为$hi$,设左数第$i$头牛与「它右边第一头高度$\geq h_i$」的牛之间有$c_i$头牛,试求$\sum ^{N}{i=1}c_{i}$。
题解
  • 应用单调栈的思想,由于牛头都是朝着右边看,所以把输入的每头牛倒序压入一个单调不增的单调栈中,每次需要弹出元素时需要处理一下答案。
AC代码
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
26
27
28
#include<cstdio>
#include<iostream>
#include<stack>
using namespace std;
typedef long long ll;
ll a[100005];
stack<ll> s;
ll ans[100005];
int main(){
ll n;
cin>>n;
for(ll i=1;i<=n;i++){
cin>>a[i];
ans[i]=1; //方便后续累加
}

for(ll i=n;i>=1;i--){
while(!s.empty()&&a[i]>a[s.top()]){
ans[i]+=ans[s.top()]; //处理答案
s.pop();
}
s.push(i);
}
ll anss=0;
for(ll i=1;i<=n;i++) anss+=ans[i]; //求和
cout<<anss-n<<endl; //多加了n,需要减去
return 0;
}

2. POJ 2259 Largest Rectangle in a Histogram

题意
  • 有一个直方图,宽度为1,现给出一串数字,表示直方图每一段的高度,要求求出直方图所围成的最大矩形面积。

    Sample Input

    1
    2
    3
    7 2 1 4 5 1 3 3
    4 1000 1000 1000 1000
    0

    Sample Output

    1
    2
    8
    4000

    比如样例1的图如下:

    image.png

题解
  • 我们发现,直方图的每一小段都对应着一个最大矩形,那么我们可以通过求这些矩形的最大值来求出答案;
  • 具体的每一段最大矩形的求法:应用严格单调递增的单调栈按照上一题的方式,先从左往右撸一遍直方图,再从右往左撸一遍,把两次单调栈的答案记录下来并求和再减1,就得到了每一段对应的最大宽度,最后再乘以长度并求$max$即可。
AC代码
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<iostream>
#include<stack>
#include<cstdio>
using namespace std;
typedef long long ll;
ll a[100005];
ll ans1[100005],ans2[100005];
ll ans;
stack<ll> s;
int main(){
ll n;
while(cin>>n&&n){
ans=0;
while(!s.empty()) s.pop();
for(ll i=1;i<=n;i++){
scanf("%lld",&a[i]);
ans1[i]=ans2[i]=1;
}
for(ll i=1;i<=n;i++){
while(!s.empty()&&a[i]<=a[s.top()]){
ans1[i]+=ans1[s.top()];
s.pop();
}
s.push(i);
}
while(!s.empty()) s.pop();
// for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
for(ll i=n;i>=1;i--){
while(!s.empty()&&a[i]<=a[s.top()]){
ans2[i]+=ans2[s.top()];
s.pop();
}
s.push(i);
}
// for(int i=1;i<=n;i++) cout<<ans2[i]<<" ";
for(ll i=1;i<=n;i++){
ans1[i]=(ans1[i]+ans2[i]-1)*a[i];
ans=max(ans1[i],ans);
}
cout<<ans<<endl;
}
return 0;
} //https://vjudge.net/problem/POJ-2559

3. 洛谷P1823 音乐会的等待

题意
  • N个人正在排队进入一个音乐会,队列中任意两个人A和B,如果他们是相邻或他们之间没有人比A或B高,那么他们是可以互相看得见的。写一个程序计算出有多少对人可以互相看见;
  • 洛谷蓝题,比前面两题多了点坑。
题解
  • 思考:我们从左至右依次观察,如果出现有一个人比前一个人高,那么这个人之后的任何一个人都不可能看到这个人之前的那一个人(写得有点绕),因此我们只需要维护一个单调递减的单调栈来处理这个问题就可以了;

  • 想要A了这题还需要考虑到当前后两个人身高相同时,后面一个人看到的人数是前一个人看到的人数+1。

AC代码
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
26
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,a[500005];
ll ans[500005];
stack<ll> s;
ll anss;
int main(){
cin>>n;
for(ll i=1;i<=n;i++){
cin>>a[i];
ans[i]=1; //方便相加
}
for(ll i=1;i<=n;i++){
while(!s.empty()&&a[i]>=a[s.top()]){
anss+=ans[s.top()];
if(a[s.top()]==a[i]) ans[i]+=ans[s.top()]; //两人等高时的情况
s.pop();
}
if(!s.empty()) anss++;
s.push(i);
// cout<<"ans"<<i<<"="<<ans[i]<<endl;
}
cout<<anss<<endl;
return 0;
} //https://www.luogu.com.cn/problem/P1823

单调队列

概念

  • 顾名思义,就是满足单调性队列结构,可以是单调递增or递减or自己定义。

练习题

1. 洛谷P1886 滑动窗口

题意
  • 有一个长为$n$的序列$a$,以及一个大小为$k$的窗口,现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值最小值

Sample Input

1
2
8 3
1 3 -1 -3 5 3 6 7

Sample Output

1
2
-1 -3 -3 -3 3 3
3 3 5 5 6 7

image.png

题解
  • 构造一个单调队列,在求最大值时单调递减,求最小值时单调递增,输出时输出每个队列中的front元素即可;

  • 蒟蒻的我使用的是STL中的deque(双向队列),中间处理过程较复杂,一定不是最优的方法,就当练习了一下deque的用法吧。

AC代码
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
26
27
28
29
30
#include<bits/stdc++.h>
using namespace std;
deque<int> q1;
deque<int> q2;
int a[1000005];
int main(){
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){//构造单调递增队列
while(!q2.empty()&&a[i]<a[q2.back()]){
q2.pop_back();
}
q2.push_back(i);
if(i-k+1>q2.front()) q2.pop_front(); //处理越界问题
if(i>=k) cout<<a[q2.front()]<<" "; //输出
}
cout<<endl;
for(int i=1;i<=n;i++){ //单调递减队列
while(!q1.empty()&&a[i]>=a[q1.back()]){
q1.pop_back();
}
q1.push_back(i);
if(i-k+1>q1.front()) q1.pop_front(); //同上
if(i>=k) cout<<a[q1.front()]<<" ";
}
return 0;
} //https://www.luogu.com.cn/problem/P1886

  • 总体来说单调栈的思维灵活性和难度更大一些,当然单调队列也十分有用,都需要细品。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!