单调队列——广告印刷

【问题描述】
最近,afy决定给TOJ印刷广告,广告牌是刷在城市的建筑物上的,城市里有紧靠着的N个建筑。
afy决定在上面找一块尽可能大的矩形放置广告牌。我们假设每个建筑物都有一个高度,
从左到右给出每个建筑物的高度H1,H2…HN,且0<Hi<=1,000,000,000,并且我们假设每个建筑物的宽度均为1。

要求输出广告牌的最大面积。

单调队列——广告印刷_第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分别为队首和队尾指针,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
*/

你可能感兴趣的:(单调队列,单调队列,广告印刷,广告印刷)