单调栈基本应用

1:给一段序列,让你求每个数的右边 第一个比它大的数 离它的距离。

例题:Bad Hair Day

经典的单调栈题目了,把题意转换成,每头牛被看了多少次,加起来就好了。所以我们要维护一个单调增的栈。(我们一般称栈顶到栈底递增的栈为单调增的栈)

代码如下:(这题数据有点问题,ans要开long long 才能过)

stack<int> st;

int main()
{
	int n; scanf("%d", &n);
	ll ans = 0;
	rep(i, 1, n){
		int x; scanf("%d", &x);
		while(!st.empty() && st.top() <= x) st.pop();
		ans += st.size();
		st.push(x);
	}
	printf("%lld\n", ans);
	return 0;
}

2,给你一个序列,让你找出这样的序列,使子序列中的最小值乘子序列长度最大

首先可以想到,我们的答案肯定是在一个元素的值 乘 (最多往左延伸或者往右延伸的距离) 中取,要直接维护这个东西的复杂度是O(n ^ 2)的,我们可以用单调栈来降低复杂度。

容易想到我们要维护的是一个单调减的栈(栈顶到栈底是递减的),这样我们就能保证栈中的每一个元素,肯定比它下面的大,那么每次进来一个比栈顶小的就可以取答案了,因为这时候一定能保证  (当前元素 与 栈顶元素之间的所有元素) 都比当前元素大,并且比s.top()大。这里的弹出就意味着这个元素的“使命”已经结束了,因为来了个更小的,限制了它的延伸,所以计算一下答案维护最大值就弹出去,维护栈的单调性。

栈为空说明左边没有比它小的元素,就可以直接乘i,不空就乘i - s.top() - 1这个区间 ,这个区间一定是合法的。

这样子,对于一个数,如果能往左边延伸(至少相邻的左边比它大),就能直接求得往左延伸的答案,如果能往右边延伸(同理),在右边遇到第一个比它小的时候也会更新到,所以答案是一定正确的。

一个小细节就是在后面加一个高度为0的点,否则可能会找不到正确答案。

84. 柱状图中最大的矩形

class Solution {
	public:
		int largestRectangleArea(vector<int>& heights) {
			int Max = 0;
			stack<int> st;
			heights.push_back(0);
			for(int i = 0; i < heights.size(); i++) {
				while(!st.empty() && heights[st.top()] >= heights[i]) {
					int now = st.top(); st.pop();
					Max = std::max(Max, heights[now] * (st.empty() ? i : (i - st.top() - 1)));
				}
				st.push(i);
			}
			return Max;
		}
};

力扣的接口式代码让我有点难受。。

 

3,给你一个序列,求出一个连续子序列,使得子序列的最小值乘子序列长度和最大

我们现在只研究没有负数的情况,有负数看这https://blog.csdn.net/swunHJ/article/details/89462086

一样的,单调栈的想法还是由贪心来的,贪心就是取每一个元素的xxx不想打字了很好想n ^ 2

怎么用单调栈去优化这个东西

显然要求出来两个数组,l[i]表示i位置的元素最多往左延伸多少,r[i]表示往右的

先考虑l数组

其实已经比较好想了,维护一个单调增的栈,如果来了个小的一直弹,否则入栈

伪代码都能写出来了,

while(!st.empty() && a[i] <= a[st.top()]) st.pop();

if(st.empty()) l[i] = 1;

else l[i] = st.top() + 1;

很好理解不多解释了,相同的办法倒着循环我们也能做出来r数组。

感觉有更好一点的办法我也不想思考了,这样就行了

例题:Feel Good

AC Code:

int n;
int a[maxn], l[maxn], r[maxn];
ll sum[maxn];
stack<int> st;

int main()
{
	scanf("%d", &n);
	rep(i, 1, n) scanf("%d", a + i);
	rep(i, 1, n) sum[i] = sum[i-1] + a[i];
	//a[++n] = -1;
	rep(i, 1, n){
		while(!st.empty() && a[i] <= a[st.top()]) st.pop();
		if(st.empty()) l[i] = 1;
		else l[i] = st.top() + 1;
		st.push(i);
	}
	while(!st.empty()) st.pop();
	Rep(i, n, 1){
		while(!st.empty() && a[i] <= a[st.top()]) st.pop();
		if(st.empty()) r[i] = n;
		else r[i] = st.top() - 1;
		st.push(i);
	}
	ll ans = -1;
	int ansl, ansr;
	rep(i, 1, n){
		ll cur = 1ll * (sum[r[i]] - sum[l[i]-1]) * a[i];
		if(cur > ans) ans = cur, ansl = l[i], ansr = r[i];
	}
	printf("%lld\n%d %d\n", ans, ansl, ansr);
	return 0;
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值