1. 程式人生 > >POJ 2559-Largest Rectangle in a Histogram 解題報告 【笛卡爾樹與單調棧】

POJ 2559-Largest Rectangle in a Histogram 解題報告 【笛卡爾樹與單調棧】

POJ 2559-Largest Rectangle in a Histogram 解題報告 【笛卡爾樹與單調棧】

Description

  A histogram is a polygon composed of a sequence of rectangles aligned at a common base line. The rectangles have equal widths but may have different heights. For example, the figure on the left shows the histogram that consists of rectangles with the heights 2, 1, 4, 5, 1, 3, 3, measured in units where 1 is the width of the rectangles:

  Usually, histograms are used to represent discrete distributions, e.g., the frequencies of characters in texts. Note that the order of the rectangles, i.e., their heights, is important. Calculate the area of the largest rectangle in a histogram that is aligned at the common base line, too. The figure on the right shows the largest aligned rectangle for the depicted histogram.

Input

  The input contains several test cases. Each test case describes a histogram and starts with an integer n, denoting the number of rectangles it is composed of. You may assume that 1 n

100000 1 \le n \le 100000 . Then follow n integers h 1 , . . . , h n h_1,...,h_n , where 0 h i 1000000000 0 \le h_i \le 1000000000 . These numbers denote the heights of the rectangles of the histogram in left-to-right order. The width of each rectangle is 1. A zero follows the input for the last test case.

Output

  For each test case output on a single line the area of the largest rectangle in the specified histogram. Remember that this rectangle must be aligned at the common base line.

Sample Input

7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

Sample Output

8
4000


思路

  在網上看的很多題解,大多數都是利用“單調棧”來求解的。這裡的“單調棧”也是基於棧的資料結構,只是在進行棧操作的時候需要滿足棧內的元素單調遞增(遞減)。然而自己直接看題解裡棧上的入棧、出棧操作,有點難以理解這種演算法,跟著演算法的步驟走了一遍也難以證明為什麼這種演算法就是對的。
  一種好理解的思路如圖1所示,因為要比較所有可以向兩方延展的矩形,所以先取高度最小的矩形,必定能向兩邊延展到最大範圍;由於是高度最小的矩形,其左邊區域的所有矩形不能跨過它向右延展,其右邊區域的所有矩形不能跨過它向左延展,這樣區域就分成了兩塊;問題就變成了遞迴求範圍內高度最小的矩形。
  求 l o g n logn 個範圍內高度最小的矩形,最簡單的想法就是依次遍歷 l o g n logn 個範圍內的各個值求最小值,時間複雜度 O ( n l o g n ) O(n \cdot logn) 。下面講的笛卡爾樹滿足上述的結構,並且可以在 O ( n ) O(n) 時間內建樹。

遞迴求解思路
圖1 遞迴求解思路

笛卡爾樹

  笛卡爾樹[1]滿足下列性質:
    1.笛卡爾樹中的結點對應序列中的每個值。
    2.樹的中序遍歷就是原序列。
    3.具有堆的性質:每個非根結點的父節點都比其大(或者小)。
  由上述的定義可以知道根結點是序列中最大(或者最小)的值,左子樹和右子樹也符合這個定義。

建樹

  以小根堆的笛卡爾樹舉例:按照笛卡爾樹的性質,笛卡爾樹中序遍歷就是原序列,所以後續插入的結點只能是某個結點的右結點,所以需要從根結點開始依次對比其右結點,直到找到符合堆性質的位置。插入的結點放在找到位置,改變了的目前堆性質的子樹放在新結點的左邊。
  如圖2所示,以在現有的笛卡爾樹中插入值為1*的結點,先從根節點開始,再依次對比其右結點,直到找到符合堆性質的插入位置。由於是之後插入的,所以1*需要是1的右結點;1的右子樹比1*先插入,所以為1*的左結點。
  由於每次需要對比根結點即其右結點,所以維護這麼一個棧即可。移到插入結點左邊的結點即為出棧操作。每個元素最多執行一次入棧和出棧操作,所以時間複雜度為 O ( n ) O(n)

圖2 插入操作

利用笛卡爾樹求解

  建立好笛卡爾樹後,因為每個矩形寬度為1,所以每個向兩邊延展的矩形的寬度為 左子樹的結點個數+右子樹結點個數+自身寬度1,遞迴遍歷更新最大值即可。

#include <stdio.h>

typedef struct TreeNode
{
	int height;
	TreeNode* left, * right, *parent;
}TreeNode;

int index = 0;
TreeNode nodes[100000];

long long int max = 0;
long long int DFS(TreeNode* root)
{
	if (root == NULL)
		return 0;

	long long int width = (1 + DFS(root->left) + DFS(root->right));
	long long int S = width * root->height;
	if (S > max)
		max = S;

	return width;
}

int main()
{
	int N = 0;
	scanf("%d", &N);

	int height = 0;
	TreeNode* stack = NULL, *root = NULL, *p;

	while (N != 0)
	{
		stack = NULL;
		root = NULL;
		index = 0;

		for (; N > 0; N--)
		{
			scanf("%d", &height);
			while (stack && height < stack->height)
				stack = stack->parent;

			nodes[index].height = height;
			nodes[index].left = nodes[index].right = nodes[index].parent = NULL;
			if (stack)
			{
				nodes[index].parent = stack;
				nodes[index].left = stack->right;
				stack = stack->right = &nodes[index++];
				
			}
			else
			{
				stack = &nodes[index++];
				stack->left = root;
				root = stack;
			}
		}

		max = 0;
		DFS(root);
		printf("%lld\n", max);
		//
		scanf("%d", &N);
	}
}


利用單調棧求解

  由於笛卡爾樹在建樹的過程中就用到了單調棧,所以單調棧的思路就是不等樹建好再去遍歷求解,而是邊建邊求結果,直接自下而上,可省下用來儲存樹結點的空間。
  從建樹的演算法中發現,移到插入結點左結點的子樹就不會改變了,遞迴求解也是將子樹的值回升傳遞給父結點,所以在用棧建樹的時候如果遇到出棧操作就可以進行回升操作了,並把值傳遞給父節點(插入結點)。

#include <stdio.h>

typedef struct
{
	int height;
	int width;
}Node;

int sp = 0;
Node stack[100000];

int main()
{
	int N = 0;

	scanf("%d", &N);
	int height = 0;
	int rise_width;
	long long int max, S;

	while (N != 0)
	{
		max = 0;
		sp = 0;
		for (; N > 0; N--)
		{
			scanf("%d", &height);
			rise_width = 0;
			while (sp > 0 && height < stack[sp - 1].height)
			{
				S = ((long long int)stack[sp - 1].height) * (stack[sp - 1].width + rise_width);
				if (S > max)
					max = S;
				rise_width += stack[sp - 1].width; //回升

				sp--;
			}

			stack[sp].height = height;
			stack[sp].width = 1 + rise_width;
			sp++;
		}

		rise_width = 0;
		while (sp > 0)
		{
			S = ((long long int)stack[sp - 1].height) * (stack[sp - 1].width + rise_width);
			if (S > max)
				max = S;
			rise_width += stack[sp - 1].width; //回升

			sp--;
		}

		printf("%lld\n", max);
		//
		scanf("%d", &N);
	}
}

參考

[1] 維基百科:笛卡爾樹