单调栈

Posted KylinLzw (●—●)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单调栈相关的知识,希望对你有一定的参考价值。

单调栈

唠嗑:寒假遇到过,当时没能弄懂,昨天在算法进阶指南看到,再一次理解之后感觉对它的认识更近了一步,写一写记录一下,也理一下思路。

分类:

  • 单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小
  • 单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大

模板:

单调栈的题目解决的代码格式比较相似,下面是单调栈主体部分的模板。

伪代码:
    
stack<int> st;
//此处一般需要给数组最后添加结束标志符,一般在st的最后一个元素加入,为了让栈中的元素可以完全弹出,这得结合题目来看,不清楚的看后面题目。
for (遍历这个数组)

	if (栈空 || 栈顶元素大于等于当前比较元素)
	
		入栈;
	
	else
	
		while (栈不为空 && 栈顶元素小于当前元素)
		
			栈顶元素出栈;
			更新结果;
		
		当前数据入栈;
	

应用:

1.

最基础的应用就是给定一组数,针对每个数,寻找它和它右边第一个比它大的数之间有多少个数。

题目:

链接.

题意:

有一群牛站成一排,每头牛都是面朝右的,每头牛可以看到他右边身高比他小的牛。给出每头牛的身高,要求每头牛能看到的牛的总数。

思路:

这就是求每个数和它右边第一个比它大的数之间的数的个数,分别求出后相加即可。朴素的做法是双重循环遍历,时间复杂度为O(n^2),用单调栈为O(n)。

代码:

#include<iostream>
#include<cmath>
#include<vector>
#include<map>
#include<stack>
#include<queue>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<list>

#define endl '\\n'
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;

const int N = 8e4+10,M=20;
const int inf=0x3f3f3f3f;
int n,m,t,x;

int main()

	int i,n,top,a[N]; //top指向栈顶元素 
	ll ans; //存储最终结果 
	stack<int> st; //st为栈,每次入栈的是每头牛的位置 
	while(~scanf("%d",&n))
	
		while(!st.empty()) st.pop(); //清空栈 
		for(i=0;i<n;i++)
			scanf("%d",&a[i]);
		a[n]=inf; //将最后一个元素设为最大值 
		ans=0;
		for(i=0;i<=n;i++)
		
			if(st.empty()||a[i]<a[st.top()])
			 //如果栈为空或入栈元素小于栈顶元素,则入栈 
				st.push(i);
			
			else 
			
				while(!st.empty()&&a[i]>=a[st.top()])
				 //如果栈不为空并且栈顶元素不大于入栈元素,则将栈顶元素出栈 
					top=st.top(); //获取栈顶元素 
					st.pop();     //栈顶元素出栈 
					//这时候也就找到了第一个比栈顶元素大的元素 
					//计算这之间牛的个数,为下标之差-1 
					ans+=(i-top-1);	
				
				st.push(i); //当所有破坏栈的单调性的元素都出栈后,将当前元素入栈 
			
		
        cout << ans << endl;
    
	return 0;


2.

给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列的长度最大。

题目:

链接.

题意:

题解:

这里引用一下《算法进阶指南》里面的讲解,觉得讲的真心不错。

代码:

#include<iostream>
#include<cmath>
#include<vector>
#include<map>
#include<stack>
#include<queue>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<list>

#define endl '\\n'
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;

const int N = 1e5+10,M=20;
const int inf=0x3f3f3f3f;
ll n,m,t,x;
ll arr[N];

int main()
   ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
   while(cin>>n)
       if(n==0)
           break;
       stack<ll> st;
       arr[n + 1] = -1;
       for (int i = 1; i <= n;i++)
       cin >> arr[i];
   
   ll ans = 0;
   for (int i = 1; i <= n + 1; i++)
   
       if (st.empty() || arr[st.top()] <= arr[i])
       
           st.push(i);
       
       else
       
           while (!st.empty()&&arr[st.top()] > arr[i])
           
               t=st.top();
               st.pop();
               ll temp = (i - t) * arr[t];
               if(temp>ans)
                   ans = temp;
                      
           st.push(t);
           arr[t] = arr[i];
       
       
       cout << ans<< endl;
   

      // system("pause");
   return 0;

3.

给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列所有元素和最大。

题目:

链接.

题意:

求序列中的最小值乘以这个序列的和的值最大,是典型的单调栈的应用。一般的思路是求每个数字所在的能使其值为区间最小值的最大区间,然后求出区间元素和乘以该值并更新结果的最大值。普通的做法时间复杂度为O(n^2),用单调栈可以达到O(n)。

题解:

用一个单调递减栈,如果栈为空或入栈元素大于等于栈顶元素,则入栈,否则将破坏栈的单调性,则将栈顶元素出栈,直到栈为空或碰到第一个小于等于入栈元素的元素。然后将最后一次出栈的栈顶元素入栈,并将其向左右拓展,并更新其对应的值。最后一次出栈的栈顶元素就是当前入栈元素可以向左拓展到的最大距离。

代码:

#include<iostream>
#include<cmath>
#include<vector>
#include<map>
#include<stack>
#include<queue>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<list>

#define endl '\\n'
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;

const int N = 1e5+10,M=20;
const int inf=0x3f3f3f3f;
int n,m,t,x;

int main()

	int i,n,pos1,pos2; //pos1和pos2记录区间的开始和结束位置 
	//tmp为临时变量,记录区间内的和;top指向栈顶元素;ans为结果;sum为前缀和 
    ll tmp,top,ans,a[100010],sum[100010];
	stack<int> st; //单调栈,记录元素位置 
	while(~scanf("%d",&n))
	
		while(!st.empty()) st.pop(); //清空栈 
		sum[0]=0;
		for(i=1;i<=n;i++)
		
			scanf("%lld",&a[i]);
			sum[i]=sum[i-1]+a[i]; //计算前缀和 
					
		a[n+1]=-1; //将最后一个设为最小值,以最后让栈内元素全部出栈 
		ans=0;
		for(i=1;i<=n+1;i++)
		
			if(st.empty()||a[i]>=a[st.top()])
			 //如果栈为空或入栈元素大于等于栈顶元素,则入栈 
				st.push(i);
			
			else 
			
				while(!st.empty()&&a[i]<a[st.top()])
				 //如果栈非空并且入栈元素小于栈顶元素,则将栈顶元素出栈 
					top=st.top();
					st.pop();					
					tmp=sum[i-1]-sum[top-1]; //计算区间内元素和 
					tmp*=a[top]; //计算结果 
					if(tmp>=ans) 
					 //更新最大值并记录位置 
						ans=tmp;
						pos1=top;
						pos2=i;
					
				
				st.push(top); //将最后一次出栈的栈顶元素入栈 
				a[top]=a[i]; //将其向左向右延伸并更新对应的值 
			
		
		printf("%lld\\n",ans);
		printf("%d %d\\n",pos1,pos2-1);
	
	return 0;

以上是关于单调栈的主要内容,如果未能解决你的问题,请参考以下文章

hdu 5033 buiding(单调栈)

[Usaco2006 Mar]Mooo 奶牛的歌声题解

kuangbin后缀数组 - I题 POJ3415 单调栈解法

poj 2059 单调栈

单调栈应用

Wannafly挑战赛18 E.极差 线段树+单调栈