单调队列——广告印刷
2013-11-04 12:09
211 查看
【问题描述】
最近,afy决定给TOJ印刷广告,广告牌是刷在城市的建筑物上的,城市里有紧靠着的N个建筑。
afy决定在上面找一块尽可能大的矩形放置广告牌。我们假设每个建筑物都有一个高度,
从左到右给出每个建筑物的高度H1,H2…HN,且0<Hi<=1,000,000,000,并且我们假设每个建筑物的宽度均为1。
要求输出广告牌的最大面积。
【输入文件】
输入文件 ad.in 中的第一行是一个数n (n<= 400,000)
第二行是n个数,分别表示每个建筑物高度H1,H2…HN,且0<Hi<=1,000,000,000。
【输出文件】
输出文件 ad.out 中一共有一行,表示广告牌的最大面积。
【输入样例】
6
5 8 4 4 8 4
【输出样例】
24
思路:
容易想到,要想使面积最大,肯定要将当前广告覆盖的楼房中,高度最小的那个楼房全部印刷上广告。所以,枚举每个建筑物的高度作为广告矩形的高度,求出在该楼房的左侧,有多少栋连续的楼房的高度大于或等于该楼房的高度,右侧同理。然后得到矩形广告的面积,找出最大面积即可。
枚举法,时间复杂度为O(n^2):
使用单调队列优化,时间复杂度为O(n):
在这里约定,以当前建筑物为矩形高度,向两侧寻找可以印刷的建筑的过程叫做——扩展。
我们来考察从第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[]记录向左扩展建筑数量,蓝色代表该建筑已经计算过,红色代表刚刚计算过的建筑物。
单调递增队列,front和rear分别为队首和队尾指针,rear指向队尾的下一个位置,初始时让0号建筑入队
1号建筑入队,队尾没有比1号建筑更高的建筑,直接入队,L[1]= 1 - mq[rear-1] - 1,结果如下:
2号建筑入队,队尾元素为1号建筑,高度为9,比2号建筑高,所以出队,新的队尾元素为0号,计算L[2] = 2 –mq[rear-1] – 1,然后2号建筑入队尾,结果如下
3号建筑入队,队尾元素为2号建筑,高度为5,与3号建筑一样高,所以出队,新的队尾元素为0号,计算L[3] = 3 –mq[rear-1] – 1,然后3号建筑入队尾,结果如下:
最终结果:
代码:
最近,afy决定给TOJ印刷广告,广告牌是刷在城市的建筑物上的,城市里有紧靠着的N个建筑。
afy决定在上面找一块尽可能大的矩形放置广告牌。我们假设每个建筑物都有一个高度,
从左到右给出每个建筑物的高度H1,H2…HN,且0<Hi<=1,000,000,000,并且我们假设每个建筑物的宽度均为1。
要求输出广告牌的最大面积。
【输入文件】
输入文件 ad.in 中的第一行是一个数n (n<= 400,000)
第二行是n个数,分别表示每个建筑物高度H1,H2…HN,且0<Hi<=1,000,000,000。
【输出文件】
输出文件 ad.out 中一共有一行,表示广告牌的最大面积。
【输入样例】
6
5 8 4 4 8 4
【输出样例】
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; }
使用单调队列优化,时间复杂度为O(n):
在这里约定,以当前建筑物为矩形高度,向两侧寻找可以印刷的建筑的过程叫做——扩展。
我们来考察从第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 | ||
队列元素 | 0 |
下标 | 0 | 1 | 2 | 3 | 4 |
H[] | -1 | 9 | 5 | 5 | -1 |
L[] | 0 |
指针 | Front | Rear | ||
队列元素 | 0 | 1 |
下标 | 0 | 1 | 2 | 3 | 4 |
H[] | -1 | 9 | 5 | 5 | -1 |
L[] | 0 | 1 |
指针 | Front | Rear | ||
队列元素 | 0 | 2 |
下标 | 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 */
相关文章推荐
- NKOJ 2150 广告印刷 单调队列
- 单调队列练习之广告印刷
- 单调队列之广告印刷问题
- NKOJ 2150 【单调队列】广告印刷
- 【单调队列】广告印刷
- 【例题】【单调队列】NKOJ2150 广告印刷
- 单调队列典型例题——广告印刷
- 单调队列水题 刷广告
- 【笔记篇】单调队列优化dp学习笔记&&luogu2569/bzoj1855股票交♂易
- HDOJ-3415(单调队列)
- poj 2823 - Sliding Window【单调队列模板】
- 【hdu 5945 】 【dp+单调队列优化】Fxx and game【求数x最少经过多少次变换能变为1,(1)如果x%k==0,那么可以x=x/k。(2)x=x-i,(1<=i<=t)】
- 用双端队列实现单调队列
- HDU 5945 Fxx and game (DP+单调队列)
- poj 3017 Cut the Sequence(DP+单调队列)
- hdu4123(树形dp+单调队列)
- soj 3139 Sliding Window(单调队列)
- [BZOJ1499][NOI2005]瑰丽华尔兹(单调队列优化DP)
- POJ 2823 Sliding Window(单调队列)
- Hdu-5289 Assignment (二分+RMQ || 单调队列)