• [线段树系列]几道不错的线段树题目题解


    Hello,我回来更新线段树系列了。

    由于目前鸽掉的文章有点多...所以只能慢慢填坑了。

    最近联赛复习的时候写了几道觉得不错的线段树题,正好可以回来填个坑。

    首先我们来看看这道题:LuoguP4145

    这道题需要我们写一个数据结构,支持下面两种操作:
    1. 区间开平方 2. 区间求和

    可以区间开平方的数据结构其实没有(别跟我说Chtholly Tree,这题没有区间赋值用不了...)。

    但是我们注意到题目中说的:向下取整。而且我们还可以发现,数列中的数大于0,小于$10^{12}$。

    我们知道sqrt(1)=1。也就是说当我们开方开到1的时候,我们就不用对那段区间进行修改了。

    经计算,我们仅需要最多6次开方操作,就能把数列中的所有数开方到1,那我们直接单点修改就好了。

    维护区间和的同时维护一下区间最值,只有区间最值>1我们才进入修改,否则我们就跳过它。

    给出代码(我写的动态开点线段树...最近比较喜欢写这个,当然你用普通线段树也是可以秒这道题的,注意到其实这题是单点修改所以不需要写pushdown函数)

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    inline int read(){
        int data=0,w=1;char ch=0;
        while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')w=-1,ch=getchar();
        while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
        return data*w;
    }
    const int N=1e5+10;
    struct Segment_Tree{
        int l,r;
        int sum,mx;
        #define lc(x) tree[x].l
        #define rc(x) tree[x].r
        #define sum(x) tree[x].sum
        #define mx(x) tree[x].mx
    }tree[N<<2];
    int ncnt,rt;
    int n,a[N],m;
    inline int insert(){
        ncnt++;
        lc(ncnt)=rc(ncnt)=sum(ncnt)=0;
        return ncnt;
    }
    void pushup(int o){
        sum(o)=sum(lc(o))+sum(rc(o));
        mx(o)=max(mx(lc(o)),mx(rc(o)));
    }
    void build(int&o,int l,int r){
        o=insert();
        if(l==r){
            sum(o)=mx(o)=a[l];return;
        }
        int mid=(l+r)>>1;
        build(lc(o),l,mid);build(rc(o),mid+1,r);
        pushup(o);
    }
    void update(int&o,int l,int r,int L,int R){
        if(!o)o=insert();
        if(l==r){
            sum(o)=sqrt(sum(o));mx(o)=sqrt(mx(o));
            return;
        }
        int mid=(l+r)>>1;
        if(L<=mid && mx(lc(o))>1)update(lc(o),l,mid,L,R);
        if(R>mid && mx(rc(o))>1)update(rc(o),mid+1,r,L,R);
        pushup(o);
    }
    int query(int o,int l,int r,int L,int R){//当前区间,查询区间 
        if(!o)return 0;
        if(L<=l && R>=r)return sum(o);
        int mid=(l+r)>>1;
        int val=0;
        if(L<=mid)val+=query(lc(o),l,mid,L,R);
        if(R>mid)val+=query(rc(o),mid+1,r,L,R);
        return val;
    }
    signed main(){
        n=read();
        for(int i=1;i<=n;i++)
            a[i]=read();
        build(rt,1,n);
        m=read();
        int k,l,r;
        for(int i=1;i<=m;i++){
            k=read();l=read();r=read();
            if(l>r)swap(l,r);
            if(k==0)
                update(rt,1,n,l,r);
            else printf("%lld
    ",query(rt,1,n,l,r));
        }
        return 0;
    }

    然后我们接着来看一下这样一道题目:
    Continuous Intervals。

    题目大意:给定一个长度为N的序列,定义连续区间[l,r]为:序列的一段子区间,满足[l,r]中的元素从小到大排序后,任意相邻两项的差值不超过1。求一共有多少个连续区间

    这是一道区间计数类的问题。对于这类问题,其实我之前接触的比较少,不太会写。最近数数题考的又比较多,所以我觉得这是一道不错的练习题。(同机房的dalao们都说水...

    对于这类题目我们有一个比较常规的解决方法就是:枚举区间的一个端点,统计以该点为答案的区间个数加入答案贡献。

    那对于这道题,我们可以枚举它的右端点r。然后对于每个枚举的右端点r,我们需要快速地求出有多少个左端点l满足连续区间的性质,我们需要在O(logn)的时间解决。

    我们来分析一下连续区间的定义,这里其实是一个非常巧妙的转换,我自己完全没有想到...

    我们发现,如果要求排好序后相邻两项的差值不超过1,那我们排好序后定义就转化为:

    $max[a_l...a_r]-min[a_l...a_r]=cnt-1$,其中cnt表示区间内不同数字的个数。

    移项得:$max-min-cnt=-1$,我们只需要维护区间的max-min-cnt就好了。

    这个可以用线段树来实现:

    我们维护两个单调栈来实现对max和min的维护,然后再用区间加减的形式更新max和min对区间内max-min-cnt的贡献。

    然后对于区间内不同数字个数,这是一个很经典的问题,我们有两种方式维护。

    首先,如果数据大小比较小,我们可以开一个pre数组记录它上一次出现的位置,然后同样的使用区间加减来维护它对max-min-cnt的贡献。这道题的数据是1e9,我们可以离散化或者开一个STL的map来做。

    然后这道题就很简单了,我们从1到n枚举右端点r,对于每一个枚举到的r,我们更新以它为右端点的区间[l,r]的max-min-cnt,最后再统计一下这个值=-1的区间个数。

    给出代码:

    #include<bits/stdc++.h>
    using namespace std;
    char buf[5000010],*pos,*End;
    #define getchar gc
    inline char gc(){
        if(pos==End){
            End=(pos=buf)+fread(buf,1,5000000,stdin);
            if(pos==End)return EOF;
        }return *pos++;
    }
    inline int read(){
        int data=0,w=1;char ch=0;
        while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')w=-1,ch=getchar();
        while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
        return data*w;
    }
    #define ll long long
    #define LOG 4
    const int N=1e5+10;
    struct SegmentTree{
        int l,r;
        ll val,cnt,add;
        #define lc(x) tree[x].l
        #define rc(x) tree[x].r
        #define val(x) tree[x].val
        #define cnt(x) tree[x].cnt
        #define add(x) tree[x].add
    }tree[N*LOG];
    int n,ncnt,rt;
    int a[N];
    void pushup(int o){
        if(val(lc(o))==val(rc(o))){
            val(o)=val(lc(o));
            cnt(o)=cnt(lc(o))+cnt(rc(o));
        }else if(val(lc(o))<val(rc(o))){
            val(o)=val(lc(o));
            cnt(o)=cnt(lc(o));
        }else{
            val(o)=val(rc(o));
            cnt(o)=cnt(rc(o));
        }
    }
    inline void pushdown(int o){
        if(add(o)){
            val(lc(o))+=add(o);add(lc(o))+=add(o);
            val(rc(o))+=add(o);add(rc(o))+=add(o);
            add(o)=0;
        }
    }
    inline int insert(){
        ++ncnt;
        lc(ncnt)=rc(ncnt)=val(ncnt)=cnt(ncnt)=add(ncnt)=0;
        return ncnt;
    }
    void build(int&o,int l,int r){
        o=insert();
        if(l==r){
            val(o)=add(o)=0;cnt(o)=1;
            return;
        }
        int mid=(l+r)>>1;
        build(lc(o),l,mid);
        build(rc(o),mid+1,r);
        pushup(o);
    }
    void update(int o,int l,int r,int L,int R,ll v){
        if(!o)o=insert();
        if(L<=l&&R>=r){
            val(o)+=v;add(o)+=v;
            return;
        }
        int mid=(l+r)>>1;
        pushdown(o);
        if(L<=mid)update(lc(o),l,mid,L,R,v);
        if(R>mid)update(rc(o),mid+1,r,L,R,v);
        pushup(o);
    }
    int main(){
        int n=read();
        for(int i=1;i<=n;i++)a[i]=read();
        build(rt,1,n);
        vector<pair<int,int> > mii(n+7),mxx(n+7);//开两个单调栈维护max和min 
        int tp1=0;int tp2=0;
        map<int,int> pre;
        ll ans=0;int cur;
        for(int i=1;i<=n;i++){
            cur=i;
            while(tp1>0&&a[i]<mii[tp1].first){
                int pos=mii[tp1-1].second;
                update(rt,1,n,pos+1,cur-1,mii[tp1].first-a[i]);
                //更新一下max-min-cnt,由于是找到了更小的min,所以这一段的max-min-cnt变小了这么多 
                --tp1;
                cur=pos+1;
            }
            mii[++tp1]=make_pair(a[i],i);//放进单调栈 
            cur=i;
            while(tp2>0&&a[i]>mxx[tp2].first){
                int pos=mxx[tp2-1].second;
                update(rt,1,n,pos+1,cur-1,a[i]-mxx[tp2].first);
                //找到了更大的max-min-cnt,也要让这一段的max加上这么多 
                --tp2;
                cur=pos+1;
            }
            mxx[++tp2]=make_pair(a[i],i);//放进单调栈 
            if(pre.find(a[i])!=pre.end()){//如果找到了和之前一样的颜色不是在最后,也就是说不是拼在一块的 
                int pos=pre[a[i]];//上一个的位置 
                update(rt,1,n,pos+1,i,-1);
                //这一段的数量在上面重复算了,上一个同样颜色的位置到自己这一段的cnt都要-1 
            }else
                   update(rt,1,n,1,i,-1);
                   //如果和前面一段的颜色拼起来了,也就是说末尾的上一个颜色和自己的颜色一样,前面的cnt就要-1 
            pre[a[i]]=i;//更新上一个这个颜色出现位置 
            if(val(rt)==-1)//如果值为-1,就更新答案 
                ans+=cnt(rt);
        }
        printf("%lld ",ans);
        return 0;
    }

    就先讲这两道题吧,以后再遇到了好题就继续往里面补充。

  • 相关阅读:
    硬件的效率与一致性
    深入理解SPI机制-服务发现机制
    spring 之7种重要设计模式
    list里放map list 放list
    jvm 三种编译
    几种不同格式的json解析
    Java知识点梳理——集合
    判断2个list中是否有相同的数据(相交)Collections.disjoint
    键相同,比较两个map中的值是否相同
    Map类型数据导出Excel--poi
  • 原文地址:https://www.cnblogs.com/light-house/p/11839805.html
Copyright © 2020-2023  润新知