1. 简介
2. 实现
模板:洛谷 P5788
对于长度为 n 的序列 a,定义 f(i) 为第 i 个元素右侧第一个大于 a[i] 的元素的下标。若不存在,则 f(i) 为 0。求 f(1)、f(2)、…、f(n)。
数据范围:n ≤ 3 × 106。
a[]: 1 4 2 3 5
f(): 2 5 4 5 0
样例解释:
思路:
vector 模拟单调栈,并用 f[] 数组记录答案。
#include<bits/stdc++.h>
using namespace std;
int n;
int a[3000010];
int f[3000010];
vector<int> stk;
int main() {
cin >> n;
for(int i = 1; i <= n; i++)
cin >> a[i];
for(int i = n; i >= 1; i--) {
while(!stk.empty() && a[stk.back()] <= a[i])
stk.pop_back();
if(!stk.empty())
f[i] = stk.back();
stk.push_back(i);
}
for(int i = 1; i <= n; i++)
cout << f[i] << " ";
return 0;
}
运行过程:
a[]: 1 4 2 3 5
index: 1 2 3 4 5
while 和 if 均不会执行,直接将 5 入栈。由于 5 的右侧没有元素,f(5) 为 0。while 不会执行。if 语句将 f(4) 赋值为 5,再将 3 入栈。while,将 2 和 3 弹出。if 语句将 f(2) 赋值为 5,再将 4 入栈。原理:
从右向左处理时,不断从栈中删除元素正是优化的关键。若当前元素大于栈顶元素,就可以弹出栈顶。因为对左侧尚未处理的元素而言,当前元素既更靠近它们,数值也更大;被弹出的元素不可能再成为“右侧第一个更大元素”。
以本题样例为例:处理元素 4 时,2 和 3 会被从栈中删除。对于之后处理的元素 1,4 比 2 和 3 更靠近 1,且数值更大,因此 2 和 3 不可能成为 1 的答案。
另外,使用 vector 模拟单调栈,编码时更加方便,具体有以下优势:
vector[i];而 stack 只能访问栈顶元素。sort、find。最后,上文为了方便,将操作称为“元素入栈”。实际上入栈的是元素下标,因为题目要求输出下标。使用栈顶元素时,也要将其视为下标,例如:
a[stk.back()]
3. 变形
示例 1:洛谷 P2866
这是一道简单变形:找出每个元素左侧第一个比它大的元素。以该元素为右端点、左侧第一个更大元素为左边界,累加所有对应区间的长度。
示例 2:洛谷 U478856
题意:对于一个长度为n的序列,求出所有区间的最大值的和。
此题难度比上题略高,不能直接套用单调栈模板。核心思路是:对于每个元素,求以它为最大值的区间数量。设 c[i] 为以 a[i] 为最大值的区间数,则答案为 Σi = 1n ci × ai。
如何计算区间数量?从左到右、从右到左各运行一次单调栈,分别求每个元素左右两侧第一个更大的元素。这样可以确定该元素作为最大值时可扩展到的最大区间,进而计算所有合法区间的数量。
4 8 3 5 7 1 9 6
l i r
index 1 2 3 4 5 6 7 8
例如,数值 7 位于第 5 个位置。两次单调栈处理后,可得到其最大区间的左右开边界 l 和 r。于是 c[i] = (i - l) × (r - i):i - l 是左端点的可能数量,r - i 是右端点的可能数量。具体到数值 7,c[5] = (5 - 2) × (7 - 5) = 6。
示例 3:洛谷 B4273
这是一道单调栈的二维变形。如下图,每个柱形的底宽相同、高度不同;给定各柱形高度,求面积最大的矩形。
此题与上题思路相通。对当前标红的柱形向左右延伸,直到无法继续延伸,便得到图中蓝框标出的矩形。若该柱形高度为 h,这就是高度为 h 的矩形所能取得的最大面积。枚举每个柱形高度并取最大值,即可得到答案。
实现时,对每个柱形使用单调栈,找出其左右两侧第一个比它矮的柱形,并据此得到可延伸区间的长度。区间长度乘以当前柱形高度,就是该高度对应的最大面积。
3 2 1 4 5 2
对于其他二维变形,也可以以此题为基础,尝试将问题转化为类似的形式。