单调栈练习题题解

单调栈

单调栈顾名思义就是让栈中的元素是单调的,要么递增,要么递减。同样它也满足栈的性质,先进后出。

  • 单调递增栈,则从栈顶到栈底的元素是严格递增的
  • 单调递减栈,则从栈顶到栈底的元素是严格递减的

练习题

  • 单调栈
  • 练习题
    • POJ3250
    • POJ2796
    • BZOJ1113
    • HDU1506
    • POJ2559
    • JDFZ2997

POJ3250

POJ3250传送门

对于每一个牛来说,能看到的数目为向右数身高比它小的个数,累加就是答案。

所以可以倒着维护一个单调递增的栈,记录i之前的弹栈数目,累加。

(正着也可以,那么就是维护对于每一头牛来说,它被多少头牛看到)

Code:

#include <stdio.h>
#define MAXN 80000+100
typedef unsigned long long llu;
llu n,ans=0;
long long s_size=-1;
llu s[MAXN];
int main()
{
    scanf("%llu",&n);
    for(llu i=1;i<=n;i++)
    {
        llu x;
        scanf("%llu",&x);
        while(s_size>=0&&x>=s[s_size])s_size--;
        s[++s_size]=x;
        ans+=s_size;
    }
    printf("%llu\n",ans);
    return 0;
}

POJ2796

POJ2796传送门
这道题比较麻烦,但是思想很重要,对于每一个点,求出向左/右第一个比它大的位置,然后O(N)枚举,找出最值。
但是需要注意两点:
开long long
预处理前缀和

Code:

#include<stdio.h>
#include<string.h>
#include<stack>
#define MAXN 100005
typedef long long ll;
using namespace std;
ll n,x,ans,l_,r_,top;
ll a[MAXN],l[MAXN],r[MAXN],s[MAXN],sum[MAXN];
ll max(ll a,ll b){return a>b?a:b;}
int main()
{
    while(~scanf("%lld",&n)&&n)
    {
        ans=0;
        l_=r_=1;
        for(ll i=1;i<=n;i++)
        {
            scanf("%lld",a+i);
            sum[i]=sum[i-1]+a[i];
        }
        s[top]=0;
        a[0]=a[n+1]=-1;
        for(ll i=1;i<=n;i++)
        {
            for(x=s[top];a[x]>=a[i];x=s[top])
            {
                top--;
            }
            l[i]=x+1;
            s[++top]=i;
        }
        top=0;
        s[top]=n+1;
        for(ll i=n;i>=1;i--)
        {
            for(x=s[top];a[x]>=a[i];x=s[top])
            {
               top--;
            }
            r[i]=x-1;
            s[++top]=i;
        }
        for(ll i=1;i<=n;i++)
        {
            ll sum_=sum[r[i]]-sum[l[i]-1];
            if(a[i]*sum_>ans)
            {
                ans=a[i]*sum_;
                l_=l[i],r_=r[i];
            }
        }
        printf("%lld\n%lld %lld\n",ans,l_,r_);
    }
    return 0;
}

BZOJ1113

——[Poi2008]海报PLA【土豪题】

BZOJ1113传送门

ans与x值无关,只与高度有关。维护一个单调递增的栈,如果有一个值在栈中出现过,那么这个高度就可以被之前的抵消。

然后再用“补集转化”的思想,最多N次才能完成,减去可以抵消的次数就是答案

Code:

#include<stdio.h>
#include<string.h>
#define MAXN 1000000+100
int s[MAXN];
int x,y,top,ans;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        while(top>=1&&y<=s[top])
        {
            if(s[top]==y)
            {
                ans++;
            }
            top--;
        }
        s[++top]=y;
    }
    printf("%d\n",n-ans);
    return 0;
}

HDU1506

HDU1506传送门

题意就是在单位长度内,每一个矩形的宽都为1,但长度可变,题意需要求最大矩形的面积。

因此我们可以用两个数组l[i],r[i]表示,第i个点向左/右 最长能扩展到第几个点,也就是第一个小于它的点。

Ans=max{a[i]*(r[i]-l[i]+1)};

在这里我们就要用的“单调栈”来进行这神奇的功能,让时间复杂度由O(n^2)变为O(n).

Code:

#include<stdio.h>
#include<string.h>
#include<stack>
#define MAXN 100005
typedef long long ll;
using namespace std;
ll n,x=0;
ll a[MAXN],l[MAXN],r[MAXN];
stack<int>s;
ll max(ll a,ll b){return a>b?a:b;}
int main()
{
    while(~scanf("%lld",&n)&&n)
    {
        for(ll i=1;i<=n;i++)
        {
            scanf("%lld",a+i);
        }
        while(!s.empty())s.pop();
        s.push(0);
        a[0]=a[n+1]=-1;
        for(ll i=1;i<=n;i++)
        {
            for(x=s.top();a[x]>=a[i];x=s.top())
            {
                s.pop();
            }
            l[i]=x+1;
            s.push(i);
        }
        while(!s.empty())s.pop();
        s.push(n+1);
        for(ll i=n;i>=1;i--)
        {
            for(x=s.top();a[x]>=a[i];x=s.top())
            {
                s.pop();
            }
            r[i]=x-1;
            s.push(i);
        }
        ll max_num=-1;
        for(ll i=1;i<=n;i++)
        {
            max_num=max(max_num,(r[i]-l[i]+1)*a[i]);
        }
        printf("%lld\n",max_num);
    }
    return 0;
}

POJ2559

POJ2559传送门

同上,一道题。

JDFZ2997

这个就不给传送门了,你懂的。

这道题同样需要维护l[],r[]两个数组,但是需要进行两遍,一次是求出以某一点为最小值最大能扩张的范围,一次是以其为最大值。

对于一个第i位的数字,如果它的l[i],r[i]已知,那么以它为最值的区间个数为(r[i]-i+1)*(i-l[i]+1)

这个性质知道后就可以求出sum_max, 以及 sum_min;因为题中要求的是所有区间的最大值-最小值的差 的和,等价于所有区间最大值的和-所有区间最小值的和。∴ans=sum_max-sum_min;

Code:

#include<stdio.h>
#include<string.h>
#define INF 990000000
#define MAXN 350000
typedef long long ll;
ll n,x,sum_max,sum_min,top;
ll a[MAXN],s[MAXN],l[MAXN],r[MAXN];
int main()
{
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++)
    {
        scanf("%lld",a+i);
    }
    s[top]=0,a[0]=a[n+1]=INF;
    for(ll i=1;i<=n;i++)
    {
        for(x=s[top];a[x]<=a[i];x=s[top])
        {
            top--;
        }
        l[i]=x+1;
        s[++top]=i;
    }
    top=0,s[top]=n+1;
    for(ll i=n;i>=1;i--)
    {
        for(x=s[top];a[x]<a[i];x=s[top])
        {
            top--;
        }
        r[i]=x-1;
        s[++top]=i;
    }
    for(ll i=1;i<=n;i++)
    {
        sum_max+=a[i]*(r[i]-i+1)*(i-l[i]+1);
    }
    //////////////////////////////////////////////////////////////
    top=0,s[top]=0,a[0]=a[n+1]=-1;
    for(ll i=1;i<=n;i++)
    {
        for(x=s[top];a[x]>=a[i];x=s[top])
        {
            top--;
        }
        l[i]=x+1;
        s[++top]=i;
    }
    top=0,s[top]=n+1;
    for(ll i=n;i>=1;i--)
    {
        for(x=s[top];a[x]>a[i];x=s[top])
        {
            top--;
        }
        r[i]=x-1;
        s[++top]=i;
    }
    for(ll i=1;i<=n;i++)
    {
        sum_min+=a[i]*(r[i]-i+1)*(i-l[i]+1);
    }
    printf("%lld\n",sum_max-sum_min);
    return 0;
}

总结:单调栈的题目一定要联想到它的性质:求出某个数的左边或者右边第一个比它大或者小的元素,而且总时间复杂度O(N)


版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-29 19:08:24

单调栈练习题题解的相关文章

浅谈—单调栈

一.概念: 单调栈的本质还是一个栈,只不过是栈的元素从栈底到栈顶单调递增或者是单调递减. 二.单调栈的维护: 每加入一个元素,将这个元素和栈顶元素相比较,(假设你维护的是一个单调递增的栈),如果当前元素大于等于栈顶元素,说明这个元素 没有破坏这个栈的单调性,直接加入:如果当前元素小于栈顶元素,直接向栈中加入元素会破坏栈的单调性,这时我们需要将栈顶元素依次弹出,知道遇到第一个 大于或者等于当前元素的元素,再将该元素压入栈中.时间复杂度O(n),所有的元素都会进栈一次. 三.单调栈的性质: (1)单

【BZOJ1007】水平可见直线(单调栈)

[BZOJ1007]水平可见直线(单调栈) 题解 Description 在xoy直角坐标平面上有n条直线L1,L2,...Ln,若在y值为正无穷大处往下看,能见到Li的某个子线段,则称Li为 可见的,否则Li为被覆盖的. 例如,对于直线: L1:y=x; L2:y=-x; L3:y=0 则L1和L2是可见的,L3是被覆盖的. 给出n条直线,表示成y=Ax+B的形式(|A|,|B|<=500000),且n条直线两两不重合.求出所有可见的直线. Input 第一行为N(0 < N < 50

[POI 2008&amp;洛谷P3467]PLA-Postering题解(单调栈)

Description Byteburg市东边的建筑都是以旧结构形式建造的:建筑互相紧挨着,之间没有空间.它们共同形成了一条长长的,从东向西延伸的建筑物链(建筑物的高度不一). Byteburg市的市长Byteasar,决定将这个建筑物链的一侧用海报覆盖住.并且想用最少的海报数量,海报是矩形的. 海报与海报之间不能重叠,但是可以相互挨着(即它们具有公共边),每一个海报都必须贴近墙并且建筑物链的整个一侧必须被覆盖(意思是:海报需要将一侧全部覆盖,并且不能超出建筑物链) 输入格式:第一行为一个整数n

CF1156E Special Segments of Permutation【题解】瞎搞 单调栈

题面:http://codeforces.com/contest/1156/problem/E Luogu翻译:https://www.luogu.com.cn/problem/CF1156E 话说Luogu要改域名了. 大意:给定一个长度为n的排列p,求有多少区间[l,r]满足,p[l]+p[r]=max{p[i]},其中l<=i<=r 据说可以笛卡尔树. 可是我不会. 那么就瞎搞. 预处理出左边第一个比a[i]大的数的位置,记为L[i] R[i]同理为右边. 这个可以用单调栈求. 然后就可

POJ 3415 Common Substrings(后缀数组+单调栈)

[题目链接] http://poj.org/problem?id=3415 [题目大意] 求出两个字符串长度大于k的公共子串的数目. [题解] 首先,很容易想到O(n2)的算法,将A串和B串加拼接符相连, 做一遍后缀数组,把分别属于A和B的所有后缀匹配,LCP-k+1就是对答案的贡献, 但是在这个基础上该如何优化呢. 我们可以发现按照sa的顺序下来,每个后缀和前面的串的LCP就是区间LCP的最小值, 那么我们维护一个单调栈,将所有单调递减的LCP值合并, 保存数量和长度,对每个属于B串的后缀更新

【NOIP数据结构专项】单调队列单调栈

[洛谷P1901 ]发射站 http://www.luogu.org/problem/show?pid=1901 题目描述 某地有 N 个能量发射站排成一行,每个发射站 i 都有不相同的高度 Hi,并能向两边(当 然两端的只能向一边)同时发射能量值为 Vi 的能量,并且发出的能量只被两边最近的且比 它高的发射站接收. 显然,每个发射站发来的能量有可能被 0 或 1 或 2 个其他发射站所接受,特别是为了安 全,每个发射站接收到的能量总和是我们很关心的问题.由于数据很多,现只需要你帮忙计 算出接收

【bzoj1345】[Baltic2007]序列问题Sequence 单调栈

题目描述 对于一个给定的序列a1, …, an,我们对它进行一个操作reduce(i),该操作将数列中的元素ai和ai+1用一个元素max(ai,ai+1)替代,这样得到一个比原来序列短的新序列.这一操作的代价是max(ai,ai+1).进行n-1次该操作后,可以得到一个长度为1的序列.我们的任务是计算代价最小的reduce操作步骤,将给定的序列变成长度为1的序列. 输入 第一行为一个整数n( 1 <= n <= 1,000,000 ),表示给定序列的长度.接下来的n行,每行一个整数ai(0

【bzoj3956】Count 单调栈+可持久化线段树

题目描述 输入 输出 样例输入 3 2 0 2 1 2 1 1 1 3 样例输出 0 3 题解 单调栈+可持久化线段树 本题是 bzoj4826 的弱化版(我为什么做题总喜欢先挑难的做QAQ) $k$对点对$(i,j)$有贡献,当且仅当$a_k=max(a_{i+1},a_{i+2},...,a_{r-1})$,且$a_k<a_i\&\&a_k<a_j$. 那么我们可以使用单调栈求出i左面第一个比它大的位置$lp[i]$,和右面第一个比它大的位置$rp[i]$,那么点对$(lp

Educational Codeforces Round 23 D. Imbalanced Array(单调栈)

题目链接:Educational Codeforces Round 23 D. Imbalanced Array 题意: 给你n个数,定义一个区间的不平衡因子为该区间最大值-最小值. 然后问你这n个数所有的区间的不平衡因子和 题解: 对每一个数算贡献,a[i]的贡献为 当a[i]为最大值时的 a[i]*(i-l+1)*(r-i+1) - 当a[i]为最小值时的a[i]*(i-l+1)*(r-i+1). 计算a[i]的l和r时,用单调栈维护.具体看代码,模拟一下就知道了. 然后把所有的贡献加起来.