实现思想
线段树图例:
![](https://img-blog.csdnimg.cn/img_convert/655ac7f576f8d669d430547e98f9008a.png)
线段树是一种二叉搜索树。他将一段区间划分为若干个单位区间,每个节点之间存储一个区间。思想类似于分治思想。
如图所示,线段树中每一个节点都存储着区间[1,10]中的信息,叶子节点的L = R。大致思想为:将大区间平分为2个小区间,每一个小区间再平分为更小的2个区间,以此类推直到每个区间的L = R,通过对这些区间的修改和查询来实现对大区间的修改和查询。
单点查找、修改的时间复杂度:O(log2n)
线段树维护的问题必须满足区间加法 例如:[1,3] + [2,4] = [1,4]。
存储方式
采用堆式存储法,即编号为k的节点左儿子编号为2k,右儿子编号为2k+1,父节点编号为{1\over2}。
通常,每一段线段树上节点储存的都是这几个变量:区间左边界,区间右边界,区间的答案。
线段树的定义:
struct node
{
int l, r, sum, lazy/*懒惰标记*/;
node(){l=r=sum=lazy=0};
}a[N];//N通常为长度的四倍或五倍
inline void update(int k)//更新节点k的sum
{
a[k].sum=a[k*2].sum+a[k*2+1].sum;
}
初始化
通常做法为遍历整颗线段树,递归到叶节点。
void bulid(int k/*k为当前节点的编号*/, int l, int r){
a[k].l=l,a[k].r=r;
if(l==r){
a[k].sum=number[l];//number数组为给定的初值(单位线段内的元素)
return;
}
int mid=(l+r)/2;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
update(k);
}
单点修改
从根节点往下 DFS,直到 L=R。
void change(int k/*当前节点的编号*/,int x/*要修改的节点编号*/,int y/*修改的值*/){
if(a[k].l==a[k].r){
a[k].sum=y;
return;
}
int mid=(a[k].l+a[k].r)/2;
if(x<=mid) change(k*2,x,y);
else change(k*2+1,x,y);
update(k);
}
区间修改
区间修改分为两步:
找到区间中全部都是要修改的点的线段树中的区间
修改这一段区间中的所有点
![](https://img-blog.csdnimg.cn/img_convert/0ebbcbcddf5acf195f698ff180b79b3e.png)
为了方便修改,此时会用到懒惰标记。
懒惰标记可用于:先修改节点,再将修改信息挂在此节点,再逐次将信息下放到子节点中。
下面是区间 +x 的代码:
void changeSegment(int k,int l,int r,int x)
//当前到了编号为k的节点,要把[l..r]区间中的所有元素的值+x
{
if(a[k].l==l&&a[k].r==r)//如果找到了全部元素都要被修改的区间
{
a[k].sum+=(r-l+1)*x;
//更新该区间的sum
a[k].lazy+=x;return;
//懒惰标记叠加
}
int mid=(a[k].l+a[k].r)/2;
if(r<=mid) changeSegment(k*2,l,r,x);
//如果被修改区间完全在左区间
else if(l>mid) changeSegment(k*2+1,l,r,x);
//如果被修改区间完全在右区间
else changeSegment(k*2,l,mid,x),changeSegment(k*2+1,mid+1,r,x);
//如果都不在,就要把修改区间分解成两块,分别往左右区间递归
update(k);
//记得更新点k的值
}
注意:某些标记无法叠加,必须先下放标记后再重新修改(例如:平方和)
下传标记
将一个节点的左右标记传给他的左右儿子,再把该节点的懒惰标记删去。
void pushdown(int k)//将点k的懒惰标记下传
{
if(a[k].l==a[k].r){a[k].lazy=0;return;}
//如果节点k已经是叶节点了,没有子节点,那么标记就不用下传,直接删除就可以了
a[k*2].sum+=(a[k*2].r-a[k*2].l+1)*a[k].lazy;
a[k*2+1].sum+=(a[k*2+1].r-a[k*2+1].l+1)*a[k].lazy;
//给k的子节点重新赋值
a[k*2].lazy+=a[k].lazy;
a[k*2+1].lazy+=a[k].lazy;
//下传点k的标记
a[k].lazy=0;//记得清空点k的标记
}
区间查询
如查询区间 [ l...r ] 中所有元素的和,也是用递归进行查询。
注意:查询之前一定要先下传标记!
int query(int k,int l,int r)
//当前到了编号为k的节点,查询[l..r]的和
{
if(a[k].lazy) pushdown(k);
//如果当前节点被打上了懒惰标记,那么就把这个标记下传,这一句其实也可以放在下一语句的后面
if(a[k].l==l&&a[k].r==r) return a[k].sum;
//如果当前区间就是询问区间,完全重合,那么显然可以直接返回
int mid=(a[k].l+a[k].r)/2;
if(r<=mid) return query(k*2,l,r);
//如果询问区间包含在左子区间中
if(l>mid) return query(k*2+1,l,r);
//如果询问区间包含在右子区间中
return query(k*2,l,mid)+query(k*2+1,mid+1,r);
//如果询问区间跨越两个子区间
}
完整代码
struct node
{
int l, r, sum, lazy
node(){l=r=sum=lazy=0};
}a[N];
inline void update(int k)
{
a[k].sum=a[k*2].sum+a[k*2+1].sum;
}
void bulid(int k, int l, int r){
a[k].l=l,a[k].r=r;
if(l==r){
a[k].sum=number[l];
return;
}
int mid=(l+r)/2;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
update(k);
}
void change(int k,int x,int y){
if(a[k].l==a[k].r){
a[k].sum=y;
return;
}
int mid=(a[k].l+a[k].r)/2;
if(x<=mid) change(k*2,x,y);
else change(k*2+1,x,y);
update(k);
}
void changeSegment(int k,int l,int r,int x)
{
if(a[k].l==l&&a[k].r==r)
{
a[k].sum+=(r-l+1)*x;
a[k].lazy+=x;return;
}
int mid=(a[k].l+a[k].r)/2;
if(r<=mid) changeSegment(k*2,l,r,x);
else if(l>mid) changeSegment(k*2+1,l,r,x);
else changeSegment(k*2,l,mid,x),changeSegment(k*2+1,mid+1,r,x);
update(k);
}
void pushdown(int k)
{
if(a[k].l==a[k].r){a[k].lazy=0;return;}
a[k*2].sum+=(a[k*2].r-a[k*2].l+1)*a[k].lazy;
a[k*2+1].sum+=(a[k*2+1].r-a[k*2+1].l+1)*a[k].lazy;
a[k*2].lazy+=a[k].lazy;
a[k*2+1].lazy+=a[k].lazy;
a[k].lazy=0;
}
int query(int k,int l,int r)
{
if(a[k].lazy) pushdown(k);
if(a[k].l==l&&a[k].r==r) return a[k].sum;
int mid=(a[k].l+a[k].r)/2;
if(r<=mid) return query(k*2,l,r);
if(l>mid) return query(k*2+1,l,r);
return query(k*2,l,mid)+query(k*2+1,mid+1,r);
}
有错误还麻烦指正!!!