poj2796 维护区间栈//单调栈

http://poj.org/problem?id=2796

题意:给你一段区间,需要你求出(在这段区间之类的最小值*这段区间所有元素之和)的最大值......

例如:

6
3 1 6 4 5 2

以4为最小值,向左右延伸,6 4 5  值为60.......

思路:解决完为这道题目,我才真正明白了单调栈的原理,它就是以某一个值为最小(最大)值,向这个值的两侧延伸,遇到大于它(小于它)的值,就将它延伸的范围扩大,当然,一般来说,要这样做的算法复杂度为o(n^2),但是借助栈这个玩意,维护其单调增(减),就可以在o(n)的时间复杂度解决这个问题。将一元素加入栈时,先判断它是否大于(小于)栈顶元素,若是大于(小于)栈顶元素,加入栈。(从这里开始只讲维护单调增栈)否则,将栈顶元素出栈,直到栈顶元素小于要加入栈的元素,在此过程中,需要维护向前延伸和向后延伸的问题,当要加入栈的元素之前有n个栈元素出栈,那么说明这n个出栈的元素都会大于或者等于要入栈的元素,此时,我们需要维护入栈元素可以向前延伸多少个元素(相当于记录它的前面有多少个元素比它大),而每个栈顶元素要向出栈了的元素延伸,因为在出栈了的元素一定是比它的大的元素(根据我维护的是单调增栈)......这样,就在o(n)的时间复杂度内解决了上述问题.........

例如:3 1 6 4 5 2

(3,1,1)  (1,2,2)  (6,3,3)  (4,4,4)  (5,5,5)  (2,6,6)

首先每个元素自己本身的前后延伸都为1,把3加入栈,1<3,把3出栈,用1的前延伸加上3的前延伸,如此变为(1,1,2),6<1,入栈,变成(1,1,2)(6,3,3),

4<6,将6出栈,4向前延伸,1向后延伸变成(1,1,3) (4,3,4)

5>4,入栈,变成(1,1,3)(4,3,4)(5,5,5)

2<5,5出栈,2向前延伸,4向后延伸,变成(1,1,3)(4,3,5)                   2还未入栈(2,5,6)

2<4,4出栈,2向前延伸,1向后延伸,变成(1,1,5) (2,3,6).....

依次类推,会发现最大的结果在(4,3,5)这里这意味着,以4为最小值的区间范围为3————5,也就是6 4 5

AC代码:

#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
int a[100005];
long long s[100005];
struct Node
{
    int k,l,r;
    int num;
};
long long sum,ans;
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        int x=0,y=0;
        sum=-100;
        ans=-100;
        s[0]=0;
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            if(i==1) s[i]=a[i];
            else s[i]=s[i-1]+a[i];
        }
        Node t,t1;
        stack<Node>q;
        t.num=a[1];
        t.k=t.l=t.r=1;
        q.push(t);
        for(int i=2; i<=n; i++)
        {
            t1.num=a[i];
            t1.k=i;
            t1.l=t1.r=1;
            while(!q.empty() && t1.num<=q.top().num)
            {
                t=q.top();
                q.pop();
                t1.l+=t.l;
                if(!q.empty()) q.top().r+=t.r;
                ans=t.num*(s[t.k+t.r-1]-s[t.k-t.l]);
                if(ans>=sum)
                {
                    sum=ans;
                    x=t.k-t.l+1;
                    y=t.k+t.r-1;
                }
            }
            q.push(t1);
        }
        while(!q.empty())
        {
            t=q.top();
            q.pop();
            if(!q.empty()) q.top().r+=t.r;
            ans=t.num*(s[t.k+t.r-1]-s[t.k-t.l]);
            if(ans>=sum)
            {
                sum=ans;
                x=t.k-t.l+1;
                y=t.k+t.r-1;
            }
        }
        if(n==0)x=y=0;
        printf("%lld\n%d %d\n",sum,x,y);
    }
    return 0;
}

网上写的比较好的代码:

#include<iostream>
#include<stack>
#include<stdio.h>
using namespace std;
#define maxx 110000
__int64 str[maxx],t[maxx];
struct node
{
    __int64 num,pre,next;    //num记录数值,pre记录向前延伸多少个,next记录向后延伸多少个,k记录本身所处的位置
    __int64 k;
};
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        stack<node>Q;
        node tmp;
        __int64 ans=-100,sum=-100,num;
        str[0]=0;
        for(__int64 i=1; i<=n; i++)
        {
            scanf("%I64d",&t[i]);
            if(i==1)
                str[i]=t[i];
            else
                str[i]=str[i-1]+t[i];
        }
        tmp.num=t[1];
        tmp.pre=1;
        tmp.next=1;
        tmp.k=1;
        Q.push(tmp);
        __int64 x=0,y=0;
        for(__int64 i=2; i<=n; i++)
        {
            node tmp1;
            tmp1.num=t[i];
            tmp1.pre=tmp1.next=1;
            tmp1.k=i;
            while(!Q.empty()&&tmp1.num<=Q.top().num)
            {
                tmp=Q.top();
                Q.pop();
                if(!Q.empty())
                    Q.top().next+=tmp.next;
                tmp1.pre+=tmp.pre;
                ans=tmp.num*(str[tmp.k+tmp.next-1]-str[tmp.k-tmp.pre]);
                if(ans>=sum)
                {
                    sum=ans;
                    x=tmp.k-tmp.pre+1;
                    y=tmp.k+tmp.next-1;
                }
            }
            Q.push(tmp1);
        }
        while(!Q.empty())
        {
            tmp=Q.top();
            Q.pop();
            if(!Q.empty())
                Q.top().next+=tmp.next;
            ans=tmp.num*(str[tmp.k+tmp.next-1]-str[tmp.k-tmp.pre]);
            if(ans>=sum)
            {
                sum=ans;
                x=tmp.k-tmp.pre+1;
                y=tmp.k+tmp.next-1;
            }
        }

        if(n==0)x=y=0;
        printf("%I64d\n%I64d %I64d\n",sum,x,y);
    }
    return 0;
}

不用栈:

//两种不同的代码,这种是不用栈的,代码少,好理解
#include <stdio.h>
#define N 100001
int lef[N],rig[N];
__int64 sum[N],a[N];
int main()
{
    int i,j,n;
    scanf("%d",&n);
    for(i = 1; i <= n; ++i)
    {
        scanf("%I64d",a + i);
        sum[i] = sum[i-1] + a[i];
        lef[i] = rig[i] = i;
    }
    for(i = 2; i <= n; ++i)
        while(lef[i] > 1 && a[lef[i]-1] >= a[i])
            lef[i] = lef[lef[i] - 1];
    for(i = n-1; i; --i)
        while(rig[i] < n && a[rig[i]+1] >= a[i])
            rig[i] = rig[rig[i] + 1];
    int ll ,rr ;  ///答案可以为0,res初始为-1
    __int64 res = -1,tmp;
    for(i = 1; i <= n; ++i)
    {
        tmp = a[i] * (sum[rig[i]] - sum[lef[i]-1]);
        if(tmp > res)
        {
            res = tmp;
            ll = lef[i];
            rr = rig[i];
        }
    }
    printf("%I64d\n%d %d\n",res,ll,rr);
    return 0;
}
时间: 2024-12-23 17:54:23

poj2796 维护区间栈//单调栈的相关文章

51NOD 1962 区间计数 单调栈+二分 / 线段树+扫描线

 区间计数 基准时间限制:1.5 秒 空间限制:262144 KB 分值: 80 两个数列 {An} , {Bn} ,请求出Ans, Ans定义如下: Ans:=Σni=1Σnj=i[max{Ai,Ai+1,...,Aj}=max{Bi,Bi+1,...,Bj}] 注:[ ]内表达式为真,则为1,否则为0. 1≤N≤3.5×1051≤Ai,Bi≤N 样例解释: 7个区间分别为:(1,4),(1,5),(2,4),(2,5),(3,3),(3,5),(4,5) Input 第一行一个整数N 第二行

POJ2796 Feel Good(单调栈)

题意:给一个非负整数序列,求哪一段区间的权值最大,区间的权值=区间所有数的和×区间最小的数. 用单调非递减栈在O(n)计算出序列每个数作为最小值能向左和向右延伸到的位置,然后O(n)枚举每个数利用前缀和O(1)计算出以这个数为最小值能得到的最大的区间权. 以前写的单调栈,三个if分支,写得繁繁杂杂的:现在重写了一下,感觉代码简洁了不少: 1 #include<cstdio> 2 using namespace std; 3 #define MAXN 111111 4 int stack[MAX

单调栈练习题题解

单调栈 单调栈顾名思义就是让栈中的元素是单调的,要么递增,要么递减.同样它也满足栈的性质,先进后出. 单调递增栈,则从栈顶到栈底的元素是严格递增的 单调递减栈,则从栈顶到栈底的元素是严格递减的 练习题 单调栈 练习题 POJ3250 POJ2796 BZOJ1113 HDU1506 POJ2559 JDFZ2997 POJ3250 POJ3250传送门 对于每一个牛来说,能看到的数目为向右数身高比它小的个数,累加就是答案. 所以可以倒着维护一个单调递增的栈,记录i之前的弹栈数目,累加. (正着也

Leetcode84. 柱状图中最大的矩形(单调栈)

84. 柱状图中最大的矩形 前置 单调栈 做法 连续区间组成的矩形,是看最短的那一块,求出每一块左边第一个小于其高度的位置,右边也同理,此块作为最短限制.需要两次单调栈 单调栈维护递增区间,每次不满足弹出栈顶,顺便利用此栈顶和当前位置计算栈顶能覆盖的长度 用来计算.仅需一次单调栈 原文地址:https://www.cnblogs.com/y2823774827y/p/11261446.html

单调栈 BZOJ1012 [JSOI2008]最大数maxnumber

1012: [JSOI2008]最大数maxnumber Time Limit: 3 Sec  Memory Limit: 162 MBSubmit: 10440  Solved: 4571[Submit][Status][Discuss] Description 现在请求你维护一个数列,要求提供以下两种操作:1. 查询操作.语法:Q L 功能:查询当前数列中末尾L 个数中的最大的数,并输出这个数的值.限制:L不超过当前数列的长度.2. 插入操作.语法:A n 功能:将n加 上t,其中t是最近一

hdu 1506 单调栈问题

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1506 题目的意思其实就是要找到一个尽可能大的矩形来完全覆盖这个矩形下的所有柱子,只能覆盖柱子,不能留空. 我们求得的面积其实就是Max{s=(right[i] - left[i] + 1)*height[i];(i>=1&&i<=n)} 每一个柱子都要尽可能向左向右延伸,使得获得最大的面积. 此时我就要用到单调栈 单调栈就是栈内元素单调递增或者单调递减的栈,单调栈只能在栈顶操作.

单调栈小结

单调栈 单调栈是解决这样一类问题 给出$n$个数,问每一个数向左第一个比它小的数是谁 如果直接暴力的话,最坏情况下肯定是$O(n^2)$的,但是单调栈可以在$O(n)$的时间内解决这类问题 实现 单调栈,顾明思议嘛,就是维护一个具有单调性的栈,至于是单调递增还是单调递减,这个视题目而定 对于上面那个问题而言,我们需要维护一个单调上升的序列 加入一个元素的时候,若当前元素比栈顶元素小,那么就不断的弹出栈顶元素,直到整个栈满足单调 那么该位置向左第一个比它小的就是栈顶 上面说的太抽象了 比如,我们有

【单调栈】hdu6325 Problem G. Interstellar Travel

从后往前更新,维护一个递减单调栈(队列) 最近很多题都是单调栈... #define _CRT_SECURE_NO_WARNINGS #include<cstdio> #include<algorithm> #include<queue> #include<iostream> //#include<deque> using namespace std; const int maxn = 1e7 + 5; int n, m, k, P, Q, R,

单调队列与单调栈用法详解

1.单调栈 单调栈是指一个栈内部的元素具有严格单调性的一种数据结构,分为单调递增栈和单调递减栈. 其具有以下两个性质: 1,满足栈底到栈顶的元素具有严格单调性. 2,满足栈的先进后出特性,越靠近栈顶的元素越后出栈. 单调队列同理,其严格单调性与单调栈相同. 但该队列中的元素满足先进先出特性,越靠近队列头的元素越先出队. 原文地址:https://www.cnblogs.com/xiefengze1/p/8495272.html