『单调栈与单调队列详解』

单调栈与单调队列详解

      • 单调栈
        • 性质
        • 模型
        • Largest Rectangle in a Histogram
        • 解析
      • 单调队列
        • 性质
        • 模型
        • 最大子序和
        • 解析

单调栈

性质

单调栈是一种特殊的栈,特殊之处在于栈内的元素都保持一个单调性,可能为单调递增,也可能为单调递减。

模型

例如下图就是一个单调递增的单调栈。
『单调栈与单调队列详解』_第1张图片
其中的元素从小到大排列。

那么,如果我们要加入一个新的元素5,5>4,符合要求,就可以直接加入。
『单调栈与单调队列详解』_第2张图片
那么如果我们需要加入一个元素3呢?
为了维护单调栈的单调性,我们需要把从栈顶开始,大于3的元素全部弹出,再加入元素3。
『单调栈与单调队列详解』_第3张图片
如图,我们弹出了4,5。
『单调栈与单调队列详解』_第4张图片
然后再加入3。
这就是单调栈的基本操作。
那么单调栈能干什么呢,我们通过一道例题来了解。

Largest Rectangle in a Histogram

题目描述

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:
『单调栈与单调队列详解』_第5张图片
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. Then follow n integers h1,…,hn, where 0<=hi<=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.

输入包含多组数据。每组数据包含一行,第一个正整数N表示有N个矩形,接下来N个正整数描述其高度。 数据以N=0结束。 数据范围参看英文题面。

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

数据规模与约定

时间限制:1s1s

空间限制:64MB

解析

这其实是一道单调栈模板题。先思考一个问题,如果题目中的矩形的高度都是单调递增的,如何得到最优解?显然有一个贪心的策略,就是以每一个矩形的高度作为最终大矩形的高度,看最宽能是多少,然后统计最优解。
但如果进来的下一矩形比上一个低,它其实相当于限制了之前矩形的高度,那么之前矩形比这个矩形高出的高度在以后的统计中就没有丝毫用处了,如果我们在这个时候把以之前矩形的高度作为最终高度的答案统计掉,那么反正以后的统计和上一个矩形没有关系,还不如把他删除。
这样,我们实际上就得到了单调栈的模型,只需要维护一个单调栈,在维护单调性的弹出操作时统计宽度,更新答案即可在 O ( n ) O(n) O(n)实际内得到最优解。
为了方便把最后剩下的,以及单调递增的矩形也统计进去,我们假设a[n+1]的位置有一个高度为0的矩形,最后将它加入单调栈时他会将所有矩形都弹出,那么答案也就完成最后的更新了。

#include
using namespace std;
int n,height[100080]={},width[100080]={},Stack[100080]={},top=0;
inline bool input()
{
	scanf("%d",&n);
	if(!n)return false;
	for(int i=1;i<=n;i++)scanf("%d",&height[i]);
	height[n+1]=0;
	return true;
}
inline long long work()
{
	long long ans=0;
	for(int i=1;i<=n+1;i++)
	{
		if(height[i]>Stack[top])
		{
			Stack[++top]=height[i];width[top]=1;
		}
		else
		{
			int Widthsum=0;
			while(Stack[top]>height[i])
			{
				Widthsum+=width[top];
				ans=max(ans,(long long)Widthsum*Stack[top]);
				top--;
			}
			Stack[++top]=height[i];width[top]=Widthsum+1;
		}
	}	
	return ans;
}
int main()
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	while(input())
	{
		printf("%lld\n",work());
		top=0;
		memset(Stack,0,sizeof(Stack));
		memset(height,0,sizeof(height));
		memset(width,0,sizeof(width));
	}
	return 0;
}

单调队列

性质

单调队列和单调栈很像,就是一个维护了单调性的队列数据结构,可以是单调递增的,也可以是单调递减的。

模型

下图是一个单调递增的单调队列模型。
『单调栈与单调队列详解』_第6张图片
其中元素也是从小到大排列。
和单调栈的操作一样,如果加入一个满足单调性的元素,例如5,那么就直接加入。
『单调栈与单调队列详解』_第7张图片
那么如果加入一个元素3呢?我们维护单调性,需要把队列尾端把大于3的元素全部弹出,那么就需要用双端队列来实现了,当然操作和单调栈是一样的。
『单调栈与单调队列详解』_第8张图片『单调栈与单调队列详解』_第9张图片
当然,队列的基本操作是可以用的。例如,元素1,2出队。
『单调栈与单调队列详解』_第10张图片
当然,一道例题。

最大子序和

Description

输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。

例如 1,-3,5,1,-2,3

当m=4时,S=5+1-2+3=7

当m=2或m=3时,S=5+1=6
Input Format

第一行两个数n,m

第二行有n个数,要求在n个数找到最大子序和
Output Format

一个数,数出他们的最大子序和
Sample Input

6 4
1 -3 5 1 -2 3

Sample Output

7

Hint

数据范围:

100%满足n,m<=300000
Limitation

各个测试点1s

解析

首先,直接找区间最大和会比较困难,我们可以尝试转换问题模型。其实,求区间和问题就相当于找前缀和的最大差值,在这道问题中,原问题就可以转换为找到两个位置x,y,使得sum[y]-sum[x]最大并且y-x≤m。
我们一重循环枚举右端点x,那么问题就转化为:找到左端点j,j∈[i-m,i-1]且j最小。
这就是我们单调队列维护的对象。试考虑两个位置j,k,如果j,k都满足要求,且k>j,s[k]

#include
using namespace std;
int n,m,num[300080]={},s[300080]={};
deque< int >Q;
inline void input()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&num[i]);
		s[i]=s[i-1]+num[i];
	}
}
inline int FindMax()
{
	int ans=-0x3f3f3f3f;
	Q.push_back(0);
	for(int i=1;i<=n;i++)
	{
		while(!Q.empty()&&Q.front()<i-m)Q.pop_front();
		ans=max(ans,s[i]-s[Q.front()]);
		while(!Q.empty()&&s[Q.back()]>=s[i])Q.pop_back();
		Q.push_back(i);
	}
	return ans;
}
int main()
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	input();
	printf("%d\n",FindMax());
	return 0;
}

你可能感兴趣的:(『单调栈与单调队列详解』)