要求输出广告牌的最大面积。
【输入文件】24
思路:
容易想到,要想使面积最大,肯定要将当前广告覆盖的楼房中,高度最小的那个楼房全部印刷上广告。所以,枚举每个建筑物的高度作为广告矩形的高度,求出在该楼房的左侧,有多少栋连续的楼房的高度大于或等于该楼房的高度,右侧同理。然后得到矩形广告的面积,找出最大面积即可。
枚举法,时间复杂度为O(n^2):
int MaxRectArea(void) { int maxArea = 0; int i, j; for (i = 0; i < n; i++) { int count = 1; //统计在当前建筑物i的左边,与h[i]相同或更高的建筑物有多少 for (j = i-1; j >= 0 && h[j] >= h[i]; j--) { count++; } //右边同理 for (j = i+1; j < n && h[j] >= h[i]; j++) { count++; } int area = count * h[i]; if (area > maxArea) { maxArea = area; } } return maxArea; }
在这里约定,以当前建筑物为矩形高度,向两侧寻找可以印刷的建筑的过程叫做——扩展。
我们来考察从第i个建筑向左扩展的情况,h数组为建筑物高度,下标为1~n:
如果我们知道,向左扩展到极限时的建筑物的下标为j的话,这时,建筑物j是第一个满足h[j]<h[i]的建筑。那么,第i个建筑物向左扩展的建筑物数量为i-j-1。
如果,对h从左向右建立一个单调递增队列mq,h数组的下标为队列元素,在新的建筑物i要入队时,将队尾所有高度大于等于h[i]的元素都出队,新的队尾mq[rear-1](rear指向队尾的下一个位置)刚好是上面所讲的向左扩展的极限下标j,因为入队顺序是从左向右的,而且在极限下标j与当前建筑下标i之间的这些建筑,因为高度大于等于h[i],所有都出队了,mq[rear-1]就刚好是极限下标j。
向右侧扩展是同样的道理,只需要对h从右向左建立一个单调递增队列即可。
为了简化操作,将h[0]和h[n+1]的值都赋为-1。
下面举一个向左侧扩展的例子:
H[]存储建筑物高度,L[]记录向左扩展建筑数量,蓝色代表该建筑已经计算过,红色代表刚刚计算过的建筑物。
下标 |
0 |
1 |
2 |
3 |
4 |
H[] |
-1 |
9 |
5 |
5 |
-1 |
L[] |
|
|
|
|
|
单调递增队列,front和rear分别为队首和队尾指针,rear指向队尾的下一个位置,初始时让0号建筑入队
指针 |
Front |
Rear |
|
|
队列元素 |
0 |
|
|
|
1号建筑入队,队尾没有比1号建筑更高的建筑,直接入队,L[1]= 1 - mq[rear-1] - 1,结果如下:
下标 |
0 |
1 |
2 |
3 |
4 |
H[] |
-1 |
9 |
5 |
5 |
-1 |
L[] |
|
0 |
|
|
|
指针 |
Front |
|
Rear |
|
队列元素 |
0 |
1 |
|
|
2号建筑入队,队尾元素为1号建筑,高度为9,比2号建筑高,所以出队,新的队尾元素为0号,计算L[2] = 2 –mq[rear-1] – 1,然后2号建筑入队尾,结果如下
下标 |
0 |
1 |
2 |
3 |
4 |
H[] |
-1 |
9 |
5 |
5 |
-1 |
L[] |
|
0 |
1 |
|
|
指针 |
Front |
|
Rear |
|
队列元素 |
0 |
2 |
|
|
3号建筑入队,队尾元素为2号建筑,高度为5,与3号建筑一样高,所以出队,新的队尾元素为0号,计算L[3] = 3 –mq[rear-1] – 1,然后3号建筑入队尾,结果如下:
下标 |
0 |
1 |
2 |
3 |
4 |
H[] |
-1 |
9 |
5 |
5 |
-1 |
L[] |
|
0 |
1 |
2 |
|
指针 |
Front |
|
Rear |
|
队列元素 |
0 |
3 |
|
|
最终结果:
下标 |
0 |
1 |
2 |
3 |
4 |
H[] |
-1 |
9 |
5 |
5 |
-1 |
L[] |
|
0 |
1 |
2 |
|
代码:
#include <iostream.h> #define MAXN 1000000 int h[MAXN+5]; //建筑物的高度 int n; //建筑物的数目 int mq[MAXN+5]; //单调队列,对内元素为建筑物高度的下标 int left[MAXN+5]; //left[i]:在第i个建筑物左侧,不比它的高度小的建筑物数量 int right[MAXN+5]; //right[i]:在第i个建筑物右侧,不比它的高度小的建筑物数量 void CalcLeft(void) { mq[0] = 0; int front = 0, rear = 1; int i; for (i = 1; i <= n; i++) { while (front < rear && h[i] <= h[mq[rear-1]]) { rear--; } left[i] = i - mq[rear-1] - 1; mq[rear++] = i; } } void CalcRight(void) { mq[0] = n + 1; int front = 0, rear = 1; int i; for (i = n; i >= 1; i--) { while (front < rear && h[i] <= h[mq[rear-1]]) { rear--; } right[i] = mq[rear-1] - i - 1; mq[rear++] = i; } } int MaxRectArea(void) { int maxArea = -1; int i; for (i = 1; i <= n; i++) { int area = (left[i] + right[i] + 1) * h[i]; if (area > maxArea) { maxArea = area; } } return maxArea; } int main(void) { while (cin >> n) { int i; for (i = 1; i <= n; i++) { cin >> h[i]; } h[0] = h[n+1] = -1; CalcLeft(); CalcRight(); for (i = 1; i <= n; i++) { cout << left[i] << ' '; } cout << endl; for (i = 1; i <= n; i++) { cout << right[i] << ' '; } cout << endl; cout << MaxRectArea() << endl; } return 0; } /* 6 5 8 4 4 8 4 6 9 5 8 2 8 8 6 9 6 8 2 8 8 */