单调栈、单调队列、线段树、LCA、二维树状数组、Bitset讲解

一、单调栈

1.问题引入

考虑这样一个问题,给出一个数字序列,一段连续的子序列的权值定义为这个子序列中最小的权值乘以子序列的长度,求最大的子序列权值,数据范围O(n)可过。

2.问题转化

稍加思索我们可以得到一个O(n^2)的算法,但是显然是太慢,我们想办法换一种枚举方式,假如我们枚举每一个数作为最小值,计算这个序列最大能扩张多长,那么这样就优化成了O(n)个数进行比较,于是只要能快速算出一个数作为最小的数最多能向左向右扩张多少就可以了,这里我们开始介绍单调栈算法。

3.单调栈

顾名思义,单调栈,首先是一个栈,且栈内元素有单调性,单调栈的维护也很简单,只要在加入元素的时候暴力弹栈维护单调性即可。
下面为了让大家理解,我模拟一下单调栈的过程。
这是一个数字序列
1 6 4 8 9 2 3
我们开始模拟一个单调递增单调栈的过程
加入1:1
加入6:1 6
加入4:1 4(弹掉6)
加入8:1 4 8
加入9:1 4 8 9
加入2:1 2(弹掉4 8 9)
加入3:1 2 3
由于每个元素至多进栈一次出栈一次,所以总时间复杂度是O(n)的。
那我们开始看单调栈如何优化上面的问题吧
我们首先观察到当后面的数弹掉前面的数的时候后面的数也一定是前面的数向右扩张时碰到的第一堵“墙”,那么在弹掉元素的时候我们就可以更新一下被弹掉数向右扩张的边界,最后栈内剩下的的数向右扩张不会遇到障碍,所以剩下的元素都可以扩张到最后的位置。
那么向左扩张的边界呢?有两种解决办法,一种就是将这个数组反过来做一遍,简单易懂。另一种就是我们观察这个单调栈,每次加入元素之后其左面的数也一定是第一个比它小的数,所以在将元素入栈的时候即可更新其向左扩张的边界。
由于只用到了单调栈,所以算一个数向左向右的边界时间复杂度就优化成了O(n),最后对每一个数计算一下答案,总时间复杂度也是O(n)。

4.单调栈能解决什么问题?

好像就能解决上面的问题QAQ

例题:

bzoj1660
这里写图片描述
题目解析:
很明显的一个思路,我们可以算出每一头牛右面第一个比它高的牛在哪里,然后答案加上see[i]-i即可,还有另一种算法更为简单,我们可以换一个考虑角度,计算每一头牛能被多少头牛看到,那么我们维护一个单调递减的单调栈,加入元素时弹完栈后栈内元素个数即为这头牛可以被多少头牛看到。
代码:

#include<bits\stdc++.h>
using namespace std;
int Stack[100000];
int top=0;
int main()
{
    int n;
    scanf("%d",&n);
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        while(top && Stack[top]<=x) top--;
        ans+=top;
        Stack[++top]=x;
    }
    cout<<ans<<endl;
    return 0;   
}

二、单调队列

1.单调队列

有了单调栈,单调队列就不用铺垫了,单调队列与单调栈唯一的区别也就是队列与栈的区别,单调队列只要维护一个首指针支持前端删除就行了。

2.单调队列的应用

(1).得到每一个连续长度为k区间的最大(最小)值

单调队列的基础应用,例如我们要求最大值我们只要维护一个单调递减的队列,当队首的下标与新加入元素的下标差超过K就弹掉队首即可。

(2).优化DP

假设有这么一个DP: F[i]=(max F[j]+)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值