线段树 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;
这些反正是比较基础的原理讲解,下一篇就是可持久化了
线段树的实现看原理还是非常简单的

浙公网安备 33010602011771号