借助单调性处理问题~单调栈(学习笔记)

单调栈: 内部元素的排列按照大小关系满足一定单调性的栈.

单调栈的性质:栈内某一个元素的左边所有元素与其满足同一大小关系,右边所有元素与其满足另一种相反的大小关系.
例如:元素1 2 3 4 5中,3的左边为1,2均小于3,右边为4,5均大于等于3.

单调栈的应用

对于可以转化某一元素为找到其右边第一个大于(小于等于)的元素的题目,可以利用单调栈的单调性优化求解.
比如说题目可以构造为某一元素找到右边第一个小于等于其的问题,那么可以构造一个严格单调递增的栈,当数组元素大于栈顶时,入栈,否则将栈顶元素出栈直至栈顶元素大于数值元素,由于对于出栈的元素,该数组元素即是其右边第一个小于等于它的元素,那么就可以通过记录中间出栈元素所需要的数据来求出对于出栈元素的答案,所有数组元素入栈后最后没有出栈的元素则不能找到右边小于等于其的元素.通过这种方法求解答案的过程由于每个元素最多出栈入栈各一次,时间复杂度为O(n).

例题讲解

题目来源: POJ - 3250
题目大意: 一群高度不完全相同的牛从左到右站成一排,每头牛只能看见它右边的比它矮的牛的发型, 若遇到一头高度大于或等于它的牛,则无法继续看到这头牛和后面的其他牛的发型。给出这些牛的高度,要求每头牛可以看到的牛的数量的和。
题目分析: 每头牛可以看到牛的数量为右边第一头比他大的牛与它之间牛的数量,可利用单调递减栈优化解决问题,开始时先将一个极大的元素入栈,从左往右依次对数组元素进行操作,若第i个元素的值大于等于栈顶的值,则将栈顶出栈直至第i个元素小于栈顶元素,记录每个中间出栈元素与第i个元素之间的下标差减一的值并统计到答案即可;

#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
ll h[800005],w[800005],pos;
int main()
{
	ll n,ans = 0;cin >> n;
	h[0] = 1e9+5;
	for(int i = 1;i <= n;++i)
	{
		ll x;
		scanf("%lld",&x);
		if(x >= h[pos])
		{
			ll cnt = 0;
			while(h[pos] <= x)
			{
				ans += cnt;
				cnt += w[pos--]; 
			}
			h[++pos] = x;
			w[pos] = cnt+1;
		}
		else
		{
			h[++pos] = x;
			w[pos] = 1;
		}
	}
	h[++pos] = 1e9+3;
	ll s = pos,cnt = 0;
	while(h[--pos]<h[s])
	{
		ans += cnt;
		cnt += w[pos]; 
	}
	cout << ans;
	return 0; 
} 

下一道例题
题目来源:POJ - 2796
题目大意: 给出一组数字,求一区间,使得区间元素总和乘以区间最小值最大,结果要求给出这个最大值和区间的左右端点。
题目分析: (以下为简洁忽略区间端点记录操作)若第i个元素为答案所在区间的最小值,第j,k个元素分别为第i个元素左右第一个小于a[i]的元素,则有ans[i] = sum[j+1,k-1]a[i],则可以借助单调栈优化解决问题,用cnt[i]表示sum[j+1,i],则ans[i] = a[i](cnt[i]+sum[i+1,k-1]),统计ans数组中最大值即可;

#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
ll a[100005],sum[100005],d[100005],ans = -1e17,pos,l,r;
int main()
{
	ll n;while(scanf("%lld",&n)!=EOF)
	{
		a[0] = -1e17;
		for(ll i = 1;i <= n;++i)
		{
			ll x;scanf("%lld",&x);
			if(x < a[pos])
			{
				ll cnt = 0;
				while(x < a[pos])
				{
					cnt += sum[pos];
					if(cnt*a[pos] > ans)
					{
						ans = cnt*a[pos];
						l = d[pos-1]+1;
						r = i-1;
					}
					--pos;
				}
				sum[++pos] = cnt+x;
				d[pos] = i;
				a[pos] = x;
			}
			else
			{
				sum[++pos] = x;
				d[pos] = i;
				a[pos] = x;
			}
		}
		ll cnt = 0;
		while(pos)
		{
			cnt += sum[pos];
			if(cnt*a[pos] > ans)
			{
				ans = cnt*a[pos];
				l = d[pos-1]+1;
				r = n;
			}
			--pos;
		}
		cout << ans;
		printf("\n"); 
		cout << l << ' ' << r << endl;
		}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值