目录
一.引入
单调栈是个什么东西?就是只单调上升、下降、不上升、不下降的序列。既可以用数组模拟,也可以就用STL的栈。时间复杂度是O(n),代码如下:
1.数组模拟
for (register int i = 1; i <= n; i ++){ //a表示数组模拟的栈,b是原数组,n是数组的长度,这是单调上升序列
if (b[i] < b[a[tail]]){
for ( ; tail > 0; tail --){
if (b[a[tail]] < b[i])
break;
}
}
a[++ tail] = i;
}
2.STL(要注意判断是否是空栈)
for (register int i = 1; i <= n; i ++){ //b是原数组,a是栈,单调上升序列
int tmp = 0;
if (!a.empty()) //注意判断是否是空栈,如果是空栈,还要a.top(),就会编译错误
tmp = a.top();
if (b[i] < b[tmp]){
while (!a.empty()){
tmp = a.top();
if (b[tmp] < b[i])
break;
a.pop();
}
}
a.push(i);
}
光有模板有什么用?题目才现实:
二.例题
1.题目:最大矩形面积
题目描述
在X轴上水平放置着 N 个条形图,这 N 个条形图就组成了一个柱状图,每个条形图都是一个矩形,每个
矩形都有相同的宽度,均为1单位长度,但是它们的高度并不相同。
例如下图,图1包含的矩形的高分别为2,1,4,5,1,3,3 单位长度,矩形的宽为1单位长度。
你的任务就是计算柱状图中以X轴为底边的最大矩形的面积。图2阴影部分就是上述例子的最大矩形面积。
输入
输入数据的第一行是一个整数 N(1≤ N ≤100000),表示柱状图包含 N 个矩形。
紧接着 N 个整数h1,...,hn(0≤ hi ≤20000, 1≤ i≤ N),表示柱状图中按从左到右顺序给出的矩形
的高度。矩形的宽度为1。
输出
输出一个整数S,表示以X轴为底边的最大矩形的面积。
输入样例
7
2 1 4 5 1 3 3
输出样例
8
2.题解
思路
这道题我们很容易想到,若要广告牌面积最大,广告牌的长不能确定,但是它的高一定等于其中某建筑的高。一定是这样,如下图:(阴影面积代表广告牌的面积)
图1: 图2:
很明显,图1的广告牌面积大于图1的广告牌面积,而且图1广告牌的高等于h[1]。
这时很容易想到一个爆搜思路:枚举每一个建筑,找到它前面第一个比它矮的建筑,和后面第一个比它矮的建筑,这两栋建筑中间有多少栋建筑,广告牌就有多长。为何如此呢?因为广告牌的每一个位置都不能悬空,如果前面的建筑比它矮,那么广告牌就不能再伸长了。
既如此,我们何不直接用单调栈呢?用一个单调上升的栈,再栈中的一个元素i的前一个元素j代表前面第一个比i建筑矮的建筑,而后面第一个比i矮的建筑就直接在每一次新元素入栈的时候判断就行了。
以样例为例:原数组h,单调栈a
1.
2.
3.
4.
5.
6.
7.
8.清空所有
相信大家一定自己已经推理出ans的计算方法了:ans = max {ans, h[a[tail]] * (i - a[tail - 1] - 1)}//前面第一个比它小的和后面第一个比它小的坐标之差,再减1。
代码
、
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define M 100005
int n, h[M], tail, a[M], ans;
int main (){
scanf ("%d", &n);
for (int i = 1; i <= n; i ++)
scanf ("%d", &h[i]);
for (register int i = 1; i <= n + 1; i ++){ //n+1是为了把栈中所有元素都踢出去
if (h[i] < h[a[tail]]){
for ( ; tail > 0; tail --){
if (h[a[tail]] <= h[i])
break;
ans = max (ans, h[a[tail]] * (i - a[tail - 1] - 1));
}
}
a[++ tail] = i;
}
printf ("%d", ans);
return 0;
}
当然也可以用STL实践,大家下去试,我在这里就不说了。
再提一个小问题:如果此题改成无限输入呢?也许大家会认为直接把代码改成while(~scanf()),再tail清零就行了。这样你会错的,万万使不得。必须把h数组清零。因为我们次循环到n+1,万一上一个数据的n大于了这个数据的n,h[n + 1]就会有值,那么你就有可能错!
三.例题升级版
1.题目:矩形牛棚
题目描述
到底是个资本家,Farmer John想通过买更多的奶牛来扩大它的生意。它需要给奶牛建造一个新的牛棚。 FJ买了一个矩形的R(1 <= R <= 3000)行C(1 <= C <= 3000)列的牧场。不幸的是,他发现某些1 x 1的区域被损坏了,所以它不可能在把整个牧场建造成牛棚了。
FJ数了一下,发现有P(1 <= p <= 30000)个1 x 1的损坏区域并且请你帮助他找到不包含损坏区域的面积最大的牛棚。 * (对同一个损坏区域可能有多次描述——aLeN.PN注)
输入
第1行: 三个空格隔开的整数 R, C, and P.
第2..P+1行: 每行包含两个空格隔开的整数, r和c, 给出一个损坏区域的行号和列号.
输出
第1行: 牛棚的最大可能面积
样例输入
3 4 2
1 3
2 1
样例输出
6
2.思路
这道题可有意思了,换成了一个二维的图,题目的图是这样:
实际上一张图就是很多个单调栈组合起来的,可以看成每行一个单调栈,也可以看成每列一个单调栈,这里我把它看成每行一个单调栈。现在的难点就是找到每一个格子所代表的建筑有多高(从上往下数)。如:(1,1)高1,(1,2)高3,(1,3)高0。如果在维护单调队列的时候边维护边找,就是一个三重循环,一定超时(O(c*r^2)),何不在预处理的时候就解决这个问题呢?
预处理方法:
首先要标记坏的格子,然后外层循环枚举列数,在内层第一个循环用一个数组prepare从小到大地存下这一列那几行的格子数坏的,第二个循环从小到大枚举这一列的每一行,再用一个下标k控制prepare数组,如果这一行小于或等于prepare[k],这个格子的高度就是prepare[k]-j,如果这一行大于prepare[k],就让k++,再prepare[k]-j就行了。
代码:
for(register int i = 1; i <= c; i ++){
int t = 0;
for(register int j = 1; j <= r; j ++){
if(flag[j][i])
prepare[++ t] = j;
}
prepare[++ t] = r + 1;
t = 1;
for(register int j = 1; j <= r; j ++){
if(j <= prepare[t]){
high[j][i] = prepare[t] - j;
}
else{
t ++;
high[j][i] = prepare[t] - j;
}
}
}
之后的二重循环单调队列就不用说了撒。
整体代码如下所示:
#include <cstdio>
#define max(a, b) a > b ? a : b
#define M 3005
using namespace std;
int r, c, p, ans, a[M], tail, high[M][M], prepare[M];
bool flag[M][M];
int main (){
scanf("%d %d %d", &r, &c, &p);
int x, y;
for(register int i = 1; i <= p; i ++){
scanf("%d %d", &x, &y);
flag[x][y] = 1;
}
for(register int i = 1; i <= c; i ++){
int t = 0;
for(register int j = 1; j <= r; j ++){
if(flag[j][i])
prepare[++ t] = j;
}
prepare[++ t] = r + 1;
t = 1;
for(register int j = 1; j <= r; j ++){
if(j <= prepare[t]){
high[j][i] = prepare[t] - j;
}
else{
t ++;
high[j][i] = prepare[t] - j;
}
}
}
for(register int i = 1; i <= r; i ++){
tail = 0;
for(register int j = 1; j <= c + 1; j ++){
if(high[i][j] < high[i][a[tail]]){
for( ; tail > 0; tail --){
if(high[i][a[tail]] <= high[i][j])
break;
ans = max (ans, high[i][a[tail]] * (j - a[tail - 1] - 1));
}
}
a[++ tail] = j;
}
}
printf("%d\n", ans);
}
四.总结
单调栈虽然好用,但是有很多坑的地方,并且有时还要用其他的处理来优化单调队列,所以要多练,多用,活学活用!谢谢!