单调栈总结_进栈和出栈的算法思想

单调栈总结_进栈和出栈的算法思想单调栈总结目录定义性质功能例题HDU1506HDU5033PKU2796PKU3250定义性质下面引自百度百科单调递增或单调减的栈,跟单调队列差不多,但是只用到它的一端,利用它可以用来解决一些ACM/ICPC和OI的题目,如RQNOJ的诺诺的队列等。单调栈是一种特殊的栈,特殊之处在于栈内的元素都保持一个单调性。假设下图是一个栈内元素的排列情况(单调递增的

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

单调栈总结

目录


定义

性质

下面引自百度百科

单调递增或单调减的栈,跟单调队列差不多,但是只用到它的一端,利用它可以用来解决一些ACM/ICPC和OI的题目,如RQNOJ 的诺诺的队列等。

单调栈是一种特殊的栈,特殊之处在于栈内的元素都保持一个单调性。
假设下图是一个栈内元素的排列情况(单调递增的栈):

By Downey

此时插入情况有两种:
(1).插入元素大于栈顶元素
当插入7时,因7 > 6,满足单调递增的条件,故可以直接加入栈
此时:
By Downey

(2).插入的元素小于栈顶元素
当插入3时,为了满足单调递增栈的性质,需要先将栈顶的4,6弹出,再插入,此时:

By Downey

功能

以上的内容和图我相信是非常容易理解的,但单调栈的作用和功能并不能得到很好的体现,故下面将用文字 + 图示的形式来展示单调栈的作用

先上结论:
利用单调栈,可以找到从左/右遍历第一个比它小/大的元素的位置

举个例子:
假设有一个单调递增的栈 S和一组数列:
a : 5 3 7 4

用数组L[i] 表示 第i个数向左遍历的第一个比它小的元素的位置

如何求L[i]?

首先我们考虑一个朴素的算法,可以按顺序枚举每一个数,然后再依此向左遍历。
但是当数列单调递减时,复杂度是严格的O(n^2)。

此时我们便可以利用单调栈在O(n)的复杂度下实现

我们按顺序遍历数组,然后构造一个单调递增栈

(1). i = 1时,因栈为空,L[1] = 0,此时再将第一个元素的位置下标1存入栈中

此时栈中情况:

By Downey
(2).i = 2时,因当前3小于栈顶元素对应的元素5,故将5弹出栈
此时栈为空
故L[2] = 0
然后将元素3对应的位置下标2存入栈中

此时栈中情况:

By Downey

(3).i = 3时,因当前7大于栈顶元素对应的元素3,故
L[3] = S.top() = 2 (栈顶元素的值)

然后将元素7对应的下标3存入栈
此时栈中情况:

By Downey

(4).i = 4时,为保持单调递增的性质,应将栈顶元素3弹出
此时 L[4] = S.top() = 2;

然后将元素4对应的下标3存入栈
此时栈中情况:

By Downey

至此 算法结束
对应的结果:
a : 5 3 7 4
L : 0 0 2 2

总结:一个元素向左遍历的第一个比它小的数的位置就是将它插入单调栈时栈顶元素的值,若栈为空,则说明不存在这么一个数。然后将此元素的下标存入栈,就能类似迭代般地求解后面的元素

代码:

Stack<int> S;
    for(int i=1 ;i<=n ;i++){
        while(S.size() && a[S.top()] >= a[i]) S.pop();

        if(S.empty())     L[i] = 0;
        else              L[i] = S.top();

        S.push(i);
    }

看到这里我相信你一定会有疑问,不知道这个功能有什么作用。
但其实通过下面的例题你会发现,用好单调栈,我们就可以解决一些看似非常复杂的问题。


例题:

HDU 1506

题目链接:

首先考虑最大面积的矩形X的左右边界的性质:

设其左边界为L,右边界为R,则其高H = min{h[i] | L <= i <= R}

此时最大面积为 (R – L + 1) * H

若此时左边界的左边那个矩形的高度 h[L-1] >= H
则左边界可以向左拓展,则新的面积为:

(R – (L-1) + 1) * H > 原面积

则与原假设条件冲突

故左边界左边的那个矩形的高度 :h[L-1] < H
同理右边界右边的那个矩形的高度: h[R+1] < H

设H = h[i]

所以左边界L是满足h[j-1] < h[i]的最大的j,即从i点向左遍历的第一个高度比i小的点的右边一个点

而右边界R是满足 h[j+1] < h[i]的最小的j,即从i点向右遍历第一个高度比i小的点的左边一个点

所以我们可以利用单调栈的性质得到每个确定点,即确定高度的最大面积矩形的左右边界,然后枚举取最大即可。

代码:

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

#define rep(i, a, b) for(int i(a); i <= (b); ++i)
#define dec(i, a, b) for(int i(a); i >= (b); --i)
#define MP make_pair

const int N = 100000 + 100;

stack<int> S;
ll h[N];
int R[N],L[N];

int main(){
    int n;
    while(~scanf("%d",&n) && n){
        for(int i=0 ;i<n ;i++)  scanf("%I64d",&h[i]);

        while(S.size()) S.pop();

        for(int i=0 ;i<n ;i++){
            while(S.size() && h[S.top()] >= h[i]) S.pop();

            if(S.empty())     L[i] = 0;
            else              L[i] = S.top() + 1;

            S.push(i);
        }

        while(S.size()) S.pop();
        for(int i=n-1 ;i>=0 ;i--){
            while(S.size() && h[S.top()] >= h[i]) S.pop();

            if(S.empty()) R[i] = n;
            else          R[i] = S.top();

            S.push(i);
        }

        ll ans = 0;
        for(int i=0 ;i<n ;i++){
            ans = max(ans,h[i] * (R[i] - L[i]));
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

HDU 5033

题目链接

这是北京区域赛的一道题,确实才接触时感觉难度很大,想了两天 加 在网上查看了题解才真正AC。这道题让我对单调栈的理解加深了不少。

题意不难理解,但难在如何利用单调栈的性质快速求解。

之前的想法是一次初始化,二分查询再跳跃式地查找区间左右边界点。
但奈何复杂度非常高,此题是明显地过不了的。

所以正确需要离线处理,先读入所有的查询,将每个查询点视为高度为0的楼,然后再通过比较两栋相邻楼顶连线的斜率大小维护一个单调栈。

一些细节性的东西网上有很多题解,便不再赘述。

#include<bits/stdc++.h>

using namespace std;

const int A = 100000 + 100;
const double PI = acos(-1.0);
const double eps = 1e-8;

class Build{
public:
    int id;
    double x,h;

    friend bool operator<(Build &a,Build &b){
        return b.x > a.x;
    }
};

Build build[A*2];
double R[2*A],L[2*A];
int S[A*2];
int num,cnt,q,n;

int dcmp(double x){
    return x < -eps ? -1 : x > eps;
}

double f(int i,int j,int type){
    if(type == 1)
        return (build[i].h - build[j].h) / (build[j].x - build[i].x);
    return     (build[j].h - build[i].h) / (build[j].x - build[i].x);
}

inline void solve(){
    cnt = 0;

    for(int i=0 ;i<num ;i++){
       while(cnt >= 2 && f(S[cnt-1],S[cnt],1) - f(S[cnt],i,1) >= 0) {
            cnt--;
       }

        if(build[i].id >= 0){
           if(cnt == 0) L[build[i].id] = 0.0;
           else         L[build[i].id] = atan(f(S[cnt],i,1)) / (2.0*PI) * 360.0;
        }
        S[++cnt] = i;
    }

    cnt = 0;
    for(int i=num-1 ;i >= 0;i--){
       while(cnt >= 2 && f(S[cnt],S[cnt-1],2) - f(i,S[cnt],2) >= 0){
            cnt--;
       }

        if(build[i].id >= 0){
           if(cnt == 0) R[build[i].id] = 0;
           else         R[build[i].id] = atan(f(i,S[cnt],2)) / (2.0*PI) * 360.0;
        }
        S[++cnt] = i;
    }

    for(int i=0 ;i<q ;i++){
        printf("%.10f\n",180.0 - (L[i] + R[i]));
    }
}

int main(){
    int T,_=1;
    scanf("%d",&T);

    while(T--){
        scanf("%d",&n);

        for(int i=0 ;i<n ;i++){
            scanf("%lf%lf",&build[i].x,&build[i].h);
            build[i].id  = -1;
        }

        scanf("%d",&q);
        for(int i=n ;i < n+q ;i++){
            scanf("%lf",&build[i].x);

            build[i].h = 0;
            build[i].id = i - n;
        }

        num = n + q;
        sort(build,build+num);

        printf("Case #%d:\n",_++);
        solve();
    }
    return 0;
}

PKU 2796

题目链接

题意很好理解,但此题不仅要求区间的边界点,还要求区间的和,所以用一个树状数组维护一个区间和即可。
另外注意全部为一个值和最大值为0的情况。

#include <iostream>
#include<iomanip>
#include <cstdio>
#include <cstdlib>
#include<cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <cctype>
#include<queue>
#include<map>
#include<stack>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

const int A = 1e5 + 100;
int n,cnt;
int a[A];
int S[A],L[A],R[A];
ll Tree[A];

int lowbit(int x){
    return x & (-x);
}

ll getsum(int pos){
    ll res = 0;
    while(pos >= 1){
        res += Tree[pos];
        pos -= lowbit(pos);
    }

    return res;
}

void add(int pos,int val){
    while(pos <= n){
        Tree[pos] += val;
        pos += lowbit(pos);
    }
}

void solve(){
    cnt = 0;

    for(int i=1 ;i<=n ;i++){
        while(cnt > 0 && a[S[cnt]] >= a[i]) cnt--;

        if(cnt == 0) L[i] = 1;
        else         L[i] = S[cnt] + 1;
        S[++cnt] = i;
        //printf("%d : L = %d\n",i,L[i]);
    }

    cnt = 0;
    for(int i=n ;i>=1 ;i--){
        while(cnt > 0 && a[S[cnt]] >= a[i]) cnt--;

        if(cnt == 0) R[i] = n;
        else         R[i] = S[cnt] - 1;
        S[++cnt] = i;
       // printf("%d : R = %d\n",i,R[i]);
    }

    ll ans = 0;
    int left,right;
    for(int i = 1;i<=n ;i++){
        ll sum = getsum(R[i]) - getsum(L[i]-1);
        sum *= a[i];

        if(sum >= ans){
            left = L[i];
            right = R[i];

            ans = sum;
        }
    }

    printf("%lld\n",ans);
    printf("%d %d\n",left,right);
}

int main(){
    while(~scanf("%d",&n)){
        memset(Tree,0,sizeof(Tree));
        for(int i=1 ;i<=n ;i++){
            scanf("%d",&a[i]);
            add(i,a[i]);
        }
        a[0] = 0;

        solve();
    }
    return 0;
}

PKU 3250

题目链接

很裸的单调栈,而且只需要跑一个方向。
简单入门题

#include <iostream>
#include<iomanip>
#include <cstdio>
#include <cstdlib>
#include<cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <cctype>
#include<queue>
#include<map>
#include<stack>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

const int INF = 1e9 + 7;
const int A   = 1e5 + 10;

int a[A],S[A],num[A];
int cnt,n;

void solve(){
    cnt = 0;

    a[n] = INF;
    for(int i=n ;i>=0 ;i--){
        while(cnt>0 && a[S[cnt]] < a[i]) cnt--;

        if(cnt == 0) num[i] = 0;
        else         num[i] = S[cnt] - i - 1;
        S[++cnt] = i;
    }

    ll ans = 0;
    for(int i=0 ;i<n ;i++){
        ans += num[i];
    }
    printf("%I64d\n",ans);
}

int main(){
    scanf("%d",&n);

    for(int i=0 ;i<n ;i++){
        scanf("%d",&a[i]);
    }
    solve();
    return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/190662.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 自然语言处理真实项目实战(20170822)

    自然语言处理真实项目实战(20170822)

    2022年3月6日
    45
  • python中的缩进规则_什么叫代码缩进

    python中的缩进规则_什么叫代码缩进引言python对缩进是敏感的,而大多教程对缩进规则,往往就几句话带过,对于没有其他语言基础的初学者,十分不友好,本文就把python常见的缩进问题做了一些整理。一、Python缩进长度及缩进字符常看到一些Python缩进错误的解读,“tab符和空格做为缩进不能混用”、“缩进一定是4个空格”。实际上并没有这些限制,例如图中的示例就可以正常运行。a=1ifa==1:print(a)else:print(1)k=1;whilek<5:

    2022年10月11日
    2
  • ubuntu16 中文输入法_如何在ubuntu中安装中文输入法

    ubuntu16 中文输入法_如何在ubuntu中安装中文输入法最近刚给笔记本装了Ubuntu+win10双系统,但是ubuntu16.04没有自带中文输入法,所以经过网上的一些经验搜索整合,分享一下安装中文输入法的心得。本文主要介绍了谷歌拼音跟ibus中文输入法的安装,由于ibus输入法问题较多,所以目前我用的是谷歌输入法。

    2022年9月26日
    2
  • intellij idea的快速配置详细使用

    intellij idea的快速配置详细使用IDEA实用教程一、IDEA简介1.简介IDEA全称IntelliJIDEA,是java语言开发的集成环境。IDEA是JetBrains公司的产品。JetBrains官网:https://www.jetbrains.com/IntelliJ在业界被公认为最好的java开发工具之一,尤其在智能代码助手、代码自动提示、重构、J2EE支持、Ant、JUnit、CVS整合、代码审查方面。了…

    2022年5月30日
    63
  • 算法-DFA算法-敏感词过滤算法(OC、Swift、Python)「建议收藏」

    前言前段时间,公司的IMSDK想做敏感词过滤,但是后端的小伙伴《比较忙》,在开产品需求会的时候想把敏感词过滤放到前端,让iOS、安卓自己搞,但是前端小伙伴写了一个方法来检测一段文本,耗时一两秒钟而且比较耗CPU,这样肯定不行的,最后后端小伙伴妥协了,把敏感词过滤放到后端了。一般的思路可能是遍历敏感词库,然后把一段文字的敏感词过滤掉,但是针对比较大的词库时(比如我们的敏感词库10万),这样非…

    2022年4月10日
    200
  • 数仓分层ods_跨境电商国内中转仓

    数仓分层ods_跨境电商国内中转仓一、ods层介绍1、保持数据原貌不做任何修改,起到备份数据的作用。2、数据采用LZO压缩,减少磁盘存储空间。100G数据可以压缩到10G以内。3、创建分区表,防止后续的全表扫描,在企业开发中大量使用分区表。4、创建外部表,在企业开发中,除了自己用的临时表,创建内部表外,绝大多数场景都是创建外部表。二、用户行为数据1、启动日志表ods_start_log//创建启动日志…

    2022年10月6日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号