线段树 3

动态开点

一般是解决值域很大但是点数比较稀疏的情况

针对值域很大的情况来说开满整个值域显然会让空间爆掉

但是因为点数很稀疏,所以大部分点开出来都是空点

那么既然开出来没有信息可以维护,

为什么不砍掉这些点,考虑在合并的时候特殊处理呢?

所以就有了动态开点,即在值域上某个点/区间从空被第一次更新的时候再给这个节点开出来

这样的话每一次开一个点,最多额外开 \(O(\log V)\) 个点,这样空间复杂度就是 \(O(q\log V)\)

线段树合并

如果需要将两个权值线段树的信息进行合并

暴力递归地进行权值更新即可

当然有一个小优化就是如果某个点没有被开出来,那么可以直接返回了

一般而言卡不满上限,时间复杂度约为 \(O(n\log n)\)

权值线段树

普通的线段树是从分划操作区间来看的

但是如果要统计值域就会比较难办

这个时候从值域的角度考虑,每个单点维护出现的总次数

这样的话就是权值线段树了

这样就可以知道每个数出现的次数,从而反推第多少大的数是多少

比较适用于权值线段树上二分

挂一些代码,主要是动态开点和线段树合并的

实际上好像也并不长
struct SgT{
    int rt[o],cnt;
    struct node{
        int l,r,val;
    }t[o];
    void add(int &p,int l,int r,int pos,int val){
        if(!p)p=++cnt;
        if(l==r)return void(t[p].val+=val);
        int mid=(l+r)/2;
        if(pos<=mid)add(t[p].l,l,mid,pos,val);
        else add(t[p].r,mid+1,r,pos,val);
        t[p].val=t[t[p].l].val+t[t[p].r].val;
    }
    int merge(int p,int q,int l,int r){
        if(!p)return q;
        if(!q)return p;
        if(l==r){
            t[p].val+=t[q].val;
            return p;
        }
        int mid=(l+r)/2;
        t[p].l=merge(t[p].l,t[q].l,l,mid);
        t[p].r=merge(t[p].r,t[q].r,mid+1,r);
        t[p].val=t[t[p].l].val+t[t[p].r].val;
        return p;
    }
    int ques(int p,int l,int r,int val){
        if(val>t[p].val)return -1;
        if(l==r)return l;
        int now=t[t[p].r].val,mid=(l+r)/2;
        if(val>now)return ques(t[p].l,l,mid,val-now);
        else return ques(t[p].r,mid+1,r,val);
    }
}T;

这些反正是比较基础的原理讲解,下一篇就是可持久化了
线段树的实现看原理还是非常简单的

posted @ 2022-07-29 11:42  2K22  阅读(28)  评论(0)    收藏  举报