从某一个元素开始,以该元素为最值,一直向前延伸找最优值(若需要两个方向的解,则再从右往左扫描一遍即可).每个元素最多进出栈一次,复杂度O(n)
应用:
①栈维护上升序列求解不小于i的最大区间:
题目链接: hdu1506 Largest Rectangle in a Histogram
求直方图能覆盖的最大矩阵的面积,则矩阵的高度为其中直方图中的最小高度。对某个直方图h[i],向两边搜寻不小于该高度的区间。
以左边界为例,使用单调栈维护一个递增序列的下标,每次取栈顶j进行比较。若h[j] >= h[i],则说明i可以延伸至j的左边界,则更新i的左边界。由于高度小于h[i]的直方图可以覆盖i的区间,大于h[i]的直方图直接被i中断,所以j对后续节点并无作用,将j弹出栈。
直到h[j] < h[i],说明i不能再向更远处延伸,将i进栈。
代码如下:
#include <cstdio>
#include <stack>
using namespace std;
#define N 100005
int a[N], l[N], r[N];
stack<int> h;
int main(){
int n;
while(scanf("%d", &n) && n){
h.push(0);
for(int i = 0; i < n; i ++){
scanf("%d", &a[i]);
l[i] = 1;
r[i] = 1;
}
for(int i = 1; i < n; i ++){
while(!h.empty() && a[i] <= a[h.top()]){
l[i] += l[h.top()];
h.pop();
}
h.push(i);
}
while(!h.empty())
h.pop();
h.push(n - 1);
for(int i = n - 2; i >= 0; i --){
while(!h.empty() && a[i] <= a[h.top()]){
r[i] += r[h.top()];
h.pop();
}
h.push(i);
}
while(!h.empty())
h.pop();
long long max_val = 0;
for(int i = 0; i < n; i ++){
long long temp = (long long)a[i] * (l[i] + r[i] - 1);
if(temp > max_val)
max_val = temp;
}
printf("%I64d\n", max_val);
}
}
②栈维护下降序列求解不大于i的最大值
题目链接:hdu3410 Passing the Message
left messenger为左边不超过h[i]的最高者j.因为若h[j] > h[i],则j一定先从别处得到message。维护一个下降序列的下标,保存当前的最高值、次高值……
每次将h[i]与栈顶比较,若大于栈顶,说明left messenger的位置不晚于这个位置,而且栈顶元素会被i挡住,所以对于i以后的点,栈顶元素也没有意义了,应该出栈。直到小于栈顶的高度,说明i的视线会被栈顶的元素挡住,则上一次的栈顶元素为left messenger。同理,求解right从右往左扫描即可。
代码如下:
#include <cstdio>
using namespace std;
#define N 50005
int h[N], stack[N], L[N], R[N];
int main(){
int n, tc, ca = 0;
scanf("%d", &tc);
while(tc --){
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &h[i]);
stack[0] = 1; //维护一个下降序列,栈中存放下标
int top = 0;
for(int i = 2; i <= n; ++i){
int cur = 0;
while(top >= 0 && h[stack[top]] < h[i])
cur = stack[top], --top;
L[i] = cur; //最后一个比h[i]小的位置
stack[++top] = i;
}
stack[0] = n, top = 0;
for(int i = n - 1; i > 0; --i){
int cur = 0;
while(top >= 0 && h[stack[top]] < h[i])
cur = stack[top], --top;
R[i] = cur;
stack[++top] = i;
}
printf("Case %d:\n", ++ca);
for(int i = 1; i <= n; ++i){
printf("%d %d\n", L[i], R[i]);
}
}
return 0;
}
单调栈不涉及区间长度,可以一直往一个方向延伸,所以只在栈顶进出栈。
当需要在一定范围内搜索时,要用单调队列,队头为该范围内的最优值,当下标不在有效范围时,队头要出队。由于篇幅有限,单调队列令开一篇记录。