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

单调栈总结_进栈和出栈的算法思想单调栈总结目录定义性质功能例题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;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • JMM内存模型介绍「建议收藏」

    JMM内存模型介绍「建议收藏」一、JMM的定义1.什么是JMM《Java虚拟机规范》中曾试图定义一种“Java内存模型”(JavaMemoryModel简称JMM)来屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。Java内存模型是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。JMM是围绕原子性,有序性、可见性展开。2.主内存与工作内存Java内存模型的主要目的是定义程

    2022年10月27日
  • 深度学习—3.Pytorch基础

    深度学习—3.Pytorch基础

  • 创建选区快捷键是什么_PS如何移动和取消选区?快捷键是什么? – PS自学网

    创建选区快捷键是什么_PS如何移动和取消选区?快捷键是什么? – PS自学网在PS中,我们可以通过移动选区来进行下一步的编辑操作,也可以通过取消选区操作来快速放弃当前选区重新选择,下面我们就一起来看看PS如何移动选区和取消选区?快捷键是什么吧!1、移动选区我们知道,创建选区有4中方法,但是移动选区时,只有使用选框工具、套索选区工具、魔棒工具和快速选择工具时,选区才能被移动。如果当前选择的是钢笔工具,选区是不能被移动的。2、移动选区操作(1)在工具箱中选择除钢笔工具之外的选…

  • mac idea 2021激活码-激活码分享

    (mac idea 2021激活码)2021最新分享一个能用的的激活码出来,希望能帮到需要激活的朋友。目前这个是能用的,但是用的人多了之后也会失效,会不定时更新的,大家持续关注此网站~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html…

  • 谷歌离线地图开发_谷歌实时在线街景地图

    谷歌离线地图开发_谷歌实时在线街景地图离线地图开发主要有两部分组成:1、获取离线地图数据;因为离线地图一般都是局域网,所以需要离线地图数据放在内网中使用;2、离线地图服务器搭建以及二次开发接口提供,离线地图是一种服务,就像我们Apache提供的WEB服务器一样,他是一种准们的地图服务:提供了包括WEB服务、TMS服务、WMTS服务等等。离线地图数据的获取:可以通过【大地图下载器】下载到。要进…

  • idea 激活码2021 3月最新注册码

    idea 激活码2021 3月最新注册码,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

发表回复

您的电子邮箱地址不会被公开。

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