51nod1215数组的宽度(单调栈)

题目来源:  Javaman
基准时间限制:1 秒 空间限制:131072 KB 分值: 80  难度:5级算法题
 收藏
 取消关注
N个整数组成的数组,定义子数组a[i]..a[j]的宽度为:max(a[i]..a[j]) - min(a[i]..a[j]),求所有子数组的宽度和。
Input
第1行:1个数N,表示数组的长度。(1 <= N <= 50000)
第2 - N + 1行:每行1个数,表示数组中的元素(1 <= A[i] <= 50000)
Output
输出所有子数组的宽度和。
Input示例
5
1
2
3
4
5
Output示例
20


题解:

这一题可以向上一篇博客一样做

首先,可以把题目转换为求每个子区间的最大值和sum1减去每个子区间最小值和sum2。

先维护一个单调增队列,可以求出以每个元素为区间最小值的向前延伸pre值和向后延伸next值。

然后维护一个单调减队列,可以求出以每个元素为区间最大值的向前延伸pre值和向后延伸next值。

然后对于每个数,通过pre和next值求出他在sum1/sum2中出现的次数(次数就是pre*next)。

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn=1e5+100;
typedef long long ll;
struct node{
    ll num;
    int pre,next,p;
    node(ll a=0,int d=0):num(a),pre(1),next(1),p(d) {}
};
ll sum[maxn];
ll a[maxn];
int main(int argc, const char * argv[]) {
    int n;
    scanf("%d",&n);
    sum[0]=0;
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    stack<node> s;
    node tmp=node(a[1],1);
    s.push(tmp);
    ll ans=0,ans1=0;
    for(int i=2;i<=n;i++)
    {
        node t=node(a[i],i);
        while(!s.empty()&&s.top().num>=t.num)
        {
            node t1=s.top();
            s.pop();
            t.pre+=t1.pre;
            if(!s.empty())
                s.top().next+=t1.next;
            ans+=t1.num*(t1.next*t1.pre);
        }
        s.push(t);
    }
    while(!s.empty())
    {
        node t1=s.top();
        s.pop();
        if(!s.empty())
            s.top().next+=t1.next;
        ans+=t1.num*(t1.next*t1.pre);
    }
    node tt=node(a[1],1);
    s.push(tt);
    for(int i=2;i<=n;i++)
    {
        node t=node(a[i],i);
        while(!s.empty()&&t.num>=s.top().num)
        {
            node t1=s.top();
            s.pop();
            if(!s.empty())
                s.top().next+=t1.next;
            t.pre+=t1.pre;
            ans1+=t1.num*(t1.pre*t1.next);
        }
        s.push(t);
    }
    while(!s.empty())
    {
        node t1=s.top();
        s.pop();
        if(!s.empty())
            s.top().next+=t1.next;
        ans1+=t1.num*(t1.pre*t1.next);
    }
    printf("%lld\n",ans1-ans);
    return 0;
}


后来,在近一步深刻理解单调栈的原理后,我又学会了另一种写法:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define fi first
#define se second
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=50000+10;
int a[maxn],s[maxn];  //此处栈中记录的是数组的下标

int main()
{
    int n;
    scanf("%d",&n);
    rep(i,1,n+1) scanf("%d",&a[i]);
    a[++n]=0;  //为了能够最后清空栈内元素,所以对队尾部赋值一个比任何元素都小的值,比如对于例子1 2 3 4这样的
    ll ans1=0,ans2=0;
    int top=0;
    rep(i,1,n+1)
    {
        while(top&&a[i]<a[s[top]])  //单调增栈
        {
            ans1+=1ll*(s[top]-s[top-1])*(i-s[top])*a[s[top]];//[s[top-1]+1,i-1]的最小值就是a[s[top]]
            top--;
        }
        s[++top]=i;
    }
    a[n]=maxn; //这个的原理同上
    top=0;
    rep(i,1,n+1)
    {
        while(top&&a[i]>a[s[top]]) //单调减栈
        {
            ans2+=1ll*(s[top]-s[top-1])*(i-s[top])*a[s[top]];
            top--;
        }
        s[++top]=i;
    }
    printf("%lld\n",ans2-ans1);
    return 0;
}













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值