从某一个元素开始,以该元素为最值,一直向前延伸找最优值(若需要两个方向的解,则再从右往左扫描一遍即可).每个元素最多进出栈一次,复杂度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; }
当需要在一定范围内搜索时,要用单调队列,队头为该范围内的最优值,当下标不在有效范围时,队头要出队。由于篇幅有限,单调队列令开一篇记录。