2018宁夏邀请赛 L.Continuous Intervals(单调栈+线段树)

题意:

给定长度为n的序列a。
问有多少个连续子序列,满足排序之后相邻元素的差值小于等于1。

数据范围:n<=1e5,1<=a(i)<=1e9

解法:
考虑枚举右端点r,统计有多少个满足条件的左端点.
对于区间[l,r],设区间最大值为ma,最小值为mi,数字种类cnt,
区间合法当且仅当ma-mi+1=cnt,即ma-mi-cnt=-1.
右移ma-mi+1总是>=cnt,
那么只需要维护每个l的ma-mi-cnt的最小值,
以及有多少个左端点l达到了最小值.

当r右移时,对于每个左端点l,ma,mi,cnt都会变化,
ma和mi用单调栈维护,cnt用last[]维护.

单调栈是找每个数作为max,min所能包含的区间范围.
last[]是找每个数对cnt有贡献的区间.

ma-mi-cnt用线段树维护,线段树同时维护一下最小值的数量.
因为对于r位置必定有ma-mi-cnt==-1,
即最小值一定是-1,因此直接ans+=T.cnt[1]即可.

参考:
https://blog.csdn.net/weixin_43823767/article/details/100188688
code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxm=5e5+5;
int a[maxm];
int n;
struct Tree{
    ll mi[maxm<<2];
    ll cnt[maxm<<2];
    ll laz[maxm<<2];
    void pp(int node){
        mi[node]=min(mi[node*2],mi[node*2+1]);
        cnt[node]=0;
        if(mi[node]==mi[node*2])cnt[node]+=cnt[node*2];
        if(mi[node]==mi[node*2+1])cnt[node]+=cnt[node*2+1];
    }
    void pd(int node){
        if(laz[node]){
            laz[node*2]+=laz[node];
            laz[node*2+1]+=laz[node];
            mi[node*2]+=laz[node];
            mi[node*2+1]+=laz[node];
            laz[node]=0;
        }
    }
    void build(int l,int r,int node){
        laz[node]=0;
        if(l==r){
            cnt[node]=1;
            mi[node]=0;
            return ;
        }
        int mid=(l+r)/2;
        build(l,mid,node*2);
        build(mid+1,r,node*2+1);
        pp(node);
    }
    void update(int st,int ed,int val,int l,int r,int node){
        if(st<=l&&ed>=r){
            laz[node]+=val;
            mi[node]+=val;
            return ;
        }
        pd(node);
        int mid=(l+r)/2;
        if(st<=mid)update(st,ed,val,l,mid,node*2);
        if(ed>mid)update(st,ed,val,mid+1,r,node*2+1);
        pp(node);
    }
}T;
signed main(){
    int TT;scanf("%d",&TT);
    int cas=1;
    while(TT--){
        scanf("%d",&n);
        T.build(1,n,1);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        ll ans=0;
        stack<int>mi,ma;
        map<int,int>last;
        for(int i=1;i<=n;i++){
            //维护ma:
            while(!ma.empty()&&a[ma.top()]<a[i]){//单调递减栈
                int x=ma.top();ma.pop();
                if(ma.empty()){
                    T.update(1,x,-a[x],1,n,1);
                }else{
                    T.update(ma.top()+1,x,-a[x],1,n,1);
                }
            }
            if(ma.empty()){
                T.update(1,i,a[i],1,n,1);
            }else{
                T.update(ma.top()+1,i,a[i],1,n,1);
            }
            ma.push(i);
            //维护mi:
            while(!mi.empty()&&a[mi.top()]>a[i]){//单调递增栈
                int x=mi.top();mi.pop();
                if(mi.empty()){
                    T.update(1,x,a[x],1,n,1);
                }else{
                    T.update(mi.top()+1,x,a[x],1,n,1);
                }
            }
            if(mi.empty()){
                T.update(1,i,-a[i],1,n,1);
            }else{
                T.update(mi.top()+1,i,-a[i],1,n,1);
            }
            mi.push(i);
            //维护cnt:
            if(last[a[i]]){
                T.update(last[a[i]]+1,i,-1,1,n,1);
            }else{
                T.update(1,i,-1,1,n,1);
            }
            last[a[i]]=i;
            //累加ans:
            ans+=T.cnt[1];
        }
        printf("Case #%d: %lld\n",cas++,ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值