对于传统线段树,我们都是把区间开满,然后要修改哪个区间,就去找包含那个区间的结点
这样子的话,就会占用很多的内存
而动态开点线段树,就是对于给定的区间[L,R],我一开始是一棵空的线段树,或者说有一个节点(L到R),当我需要插入某个下标为k的数的时候,我再一路创建所表示区间包含k的结点并修改(如果这个结点先前没有被创建的话),从而节省了大量的内存
实现代码如下:
const int N=2E5+10;
#define mid (l+r>>1)
//ls[u]为u的左儿子
//rs[u]为u的右儿子
//sum[u]为u的权值和
int ls[N*40],rs[N*40],sum[N*40];
//我们要开N棵这样子的线段树
int root[N],tot;//利用左右儿子结点由下往上地更新父结点
void pushup(int u){sum[u]=sum[ls[u]]+sum[rs[u]];
}
//u为要修改的结点
//l,r为结点所表示的区间
//p为要查找的下标p
//k为要修改的权值
//这里为单点修改
//区间修改的类似,要修改区间[x,y]的话,把int p换成int x,int y就可以了
void addtree(int &u,int l,int r,int p,int k){//如果该结点先前未被创建的话if(!u){//创建节点u=++tot;}//当递归到了叶子结点,也就是找到了下标p所在的结点if(l==r){sum[u]+=k;return;}//如果没递归到叶子结点//如果p在左半边区间,递归左半边if(p<=mid){addtree(ls[u],l,mid,p,k);}//否则递归右半边区间else{addtree(rs[u],mid+1,r,p,k);}//最后,由于递归更新了u的ls和rs,我们需要用rs和ls来更新一下u的信息pushup(u);
}
对于动态开点的线段树,我们一般会根据不同情况开很多棵这样子的树
最后我们需要将这样子的树合并来统计信息
也就是需要合并线段树
合并的思路很简单
假设合并以x为根和以y为根的两棵树
当我们递归到一个结点,如果x或y在这里没有结点,那么直接搬用对方的结点过来即可(如果双方在这里都没有结点,那么这里就仍然为空节点了)
然后左右子树递归
当我们递归到叶子结点的时候,合并权值
然后自下而上地pushup即可
实现代码如下:
//x,y为要合并的以x为根,以y为根的两棵树
//l,r为x结点与y结点所表示的区间
int merge(int x,int y,int l,int r){//如果有一方为空节点//直接返回另一方即可if(!x||!y){return x+y;} //递归到了叶子结点if(l==r){sum[x]+=sum[y];return x;}//递归左右子树ls[x]=merge(ls[x],ls[y],l,mid);rs[x]=merge(rs[x],rs[y],mid+1,r);//更新x的信息pushup(x);//返回合并后的根return x;
}