【模板】数据结构 - 线段树

本文深入探讨了C++中的线段树数据结构,包括基本的构建、查询和更新操作,以及如何处理区间加值、区间赋值和区间异或等复杂操作。文章详细解释了pushdown和pushup函数的作用,以及它们在更新过程中的应用。

注意在C++中,加减运算比移位运算优先级高,移位运算比按位运算优先级高。

封装版:

const int MAXM=200000;                                    //MAXM 为线段最大长度
int a[MAXM+5],st[(MAXM<<2)+5];                            //a数组为原数组st数组为四倍大小线段树

struct SegmentTree {
    int n;  										      //线段树的大小
    int op_init=0;
    inline int op_cal(int o1,int o2) { return o1+o2; }

    void _build(int o,int l,int r) {                      //o当前结点,l左端点,r右端点,_build(1,1,n)
        if(l==r) { st[o]=a[l]; return; }
        int m=l+((r-l)>>1);
        _build(o<<1,l,m);
        _build((o<<1)|1,m+1,r);
        st[o]=op_cal(st[o<<1],st[(o<<1)|1]);              //子结点建立完成后,更新父节点
    }

    //单点修改
    void _update(int o,int l,int r,int ind,int ans) {     //ind为叶结点,ans为值,_update(1,1,n,ind,ans);
        if(l==r) { st[o]=ans; return; }
        int m=l+((r-l)>>1);
        if(ind<=m) _update(o<<1,l,m,ind,ans);
        else _update((o<<1)|1,m+1,r,ind,ans);
        st[o]=op_cal(st[o<<1],st[(o<<1)|1]);              //子节点更新后,更新父节点
    }

    //查询
    int _query(int o,int l,int r,int ql,int qr) {         //ql、qr为查询区间左右端点,_query(1,1,n,a,b)
        if(ql>r||qr<l) return op_init;                    //区间不相交,返回无关值
        if(ql<=l&&qr>=r) return st[o];                    //被查询区间覆盖,返回当前信息
        int m=l+((r-l)>>1);                               //否则综合子结点的信息返回
        return op_cal(_query(o<<1,l,m,ql,qr),_query((o<<1)|1,m+1,r,ql,qr));
    }

    void build(int tn) { n=tn; _build(1,1,n); }
    void update(int ind,int ans) { _update(1,1,n,ind,ans); }
    int query(int l,int r) { return _query(1,1,n,l,r); }
};

区间加值:

void pushup(int o){          //pushup函数,该函数本身是将当前结点用左右子节点的信息更新,此处求区间和,用于update中将结点信息传递完返回后更新父节点
    st[o]=st[o<<1]+st[o<<1|1];
}
  
void pushdown(int o,int l,int r){  //pushdown函数,将o结点的信息传递到左右子节点上
    if(add[o]){             //当父节点有更新信息时才向下传递信息
        add[o<<1]+=add[o];      //左右儿子结点均加上父节点的更新值
        add[o<<1|1]+=add[o];
        int m=l+((r-l)>>1);
        st[o<<1]+=add[o]*(m-l+1);  //左右儿子结点均按照需要加的值总和更新结点信息
        st[o<<1|1]+=add[o]*(r-m);
        add[o]=0;                //信息传递完之后就可以将父节点的更新信息删除
    }
}
 
void update(int o,int l,int r,int ql,int qr,int addv){  //ql、qr为需要更新的区间左右端点,addv为需要增加的值
    if(ql<=l&&qr>=r){                      //与单点更新一样,当当前结点被需要更新的区间覆盖时
        add[o]+=addv;                      //更新该结点的所需更新信息
        st[o]+=addv*(r-l+1);                //更新该结点信息
        return;                    //根据lazy思想,由于不需要遍历到下层结点,因此不需要继续向下更新,直接返回
    }
    
    pushdown(o,l,r);                  //将当前结点的所需更新信息传递到下一层(其左右儿子结点)
    int m=l+((r-l)>>1);
    if(ql<=m)update(o<<1,l,m,ql,qr,addv);     //当需更新区间在当前结点的左儿子结点内,则更新左儿子结点
    if(qr>=m+1)update(o<<1|1,m+1,r,ql,qr,addv);   //当需更新区间在当前结点的右儿子结点内,则更新右儿子结点
    pushup(o);                  //递归回上层时一步一步更新回父节点
}

ll query(int o,int l,int r,int ql,int qr){    //ql、qr为需要查询的区间
    if(ql<=l&&qr>=r) return st[o];      //若当前结点覆盖区间即为需要查询的区间,则直接返回当前结点的信息
    pushdown(o,l,r);                  //将当前结点的更新信息传递给其左右子节点
    int m=l+((r-l)>>1);
    ll ans=0;                      //所需查询的结果
    if(ql<=m)ans+=query(o<<1,l,m,ql,qr);     //若所需查询的区间与当前结点的左子节点有交集,则结果加上查询其左子节点的结果
    if(qr>=m+1)ans+=query(o<<1|1,m+1,r,ql,qr); //若所需查询的区间与当前结点的右子节点有交集,则结果加上查询其右子节点的结果
   return ans; 
}

区间赋值:只是把pushdown和update的+=改成=

void pushup(int o){
     st[o]=st[o<<1]+st[o<<1|1];
 }
 
 void pushdown(int o,int l,int r){  //pushdown和区间加值不同,改值时修改结点信息只需要对修改后的信息求和即可,不用加上原信息
     if(change[o]){
         int c=change[o];
         change[o<<1]=c;
         change[o<<1|1]=c;
         int m=l+((r-l)>>1);
         st[o<<1]=(m-l+1)*c;
         st[o<<1|1]=(r-m)*c;
         change[o]=0;
     }
 }
 
 void update(int o,int l,int r,int ql,int qr,int c){
     if(ql<=l&&qr>=r){         //同样更新结点信息和区间加值不同
         change[o]=c;
         st[o]=(r-l+1)*c;
         return;
     }
     
     pushdown(o,l,r);
     int m=l+((r-l)>>1);
     if(ql<=m)update(o<<1,l,m,ql,qr,c);
     if(qr>=m+1)update(o<<1|1,m+1,r,ql,qr,c);
     pushup(o);
 }
 
 int query(int o,int l,int r,int ql,int qr){
     if(ql<=l&&qr>=r) return st[o];
     pushdown(o,l,r);
     int m=l+((r-l)>>1);
     int ans=0;
     if(ql<=m)ans+=query(o<<1,l,m,ql,qr);
     if(qr>=m+1)ans+=query(o<<1|1,m+1,r,ql,qr);
     return ans;
 }

线段树区间异或:每一位开一个线段树,每个节点维护对应线段的异或和。

 

好像主席树(可持久化线段树)也是线段树?

解决第k大问题应该用什么?应该比划分树好,划分树看起来是静态的,主席树只是常数稍大。

 

压行版:

const int MAXM=200000;
int a[MAXM+5],st[(MAXM<<2)+5];

void build(int o,int l,int r){
    if(l==r) st[o]=a[l];
    else{
        int m=l+((r-l)>>1);
        build(o<<1,l,m);
        build(o<<1|1,m+1,r);
        st[o]=max(st[o<<1],st[o<<1|1]);
    }
}

void update(int o,int l,int r,int id,int v){
    if(l==r) st[o]=v;
    else{
        int m=l+((r-l)>>1);
        if(id<=m) update(o<<1,l,m,id,v);
        else update(o<<1|1,m+1,r,id,v);
        st[o]=max(st[o<<1],st[o<<1|1]);
    }
}

int query(int o,int l,int r,int a,int b){
    if(r<a||l>b) return -1;
    if(a<=l&&r<=b) return st[o];
    int m=l+((r-l)>>1);
    int p1=query(o<<1,l,m,a,b),p2=query(o<<1|1,m+1,r,a,b);
    return max(p1,p2);
}

扫描线线段树,注意每个点坐标包含它右边一单位长度的线段(整数扫描线?)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值