[OI] 线段树
打线段树的时候容易踩的坑
- 数组开四倍
- 建树的时候叶节点赋值之后要return
- 单点修改的时候叶节点赋值之后也要return
- 单点修改的时候,要根据当前mid与搜索位置判断是要修改左子树还是右子树
- 区间查询的时候,边界条件不是 $ t[id].l=x \And\And t[id].r=y $,而是 $ t[id].l \ge x \And\And t[id].r \le y $,否则在分段搜索更小的区间时会死循环.
- 因为我们建树的时候,区间中间点划分是向下取整的,因此我们在区间查询的时候,单独返回左孩子的条件是 \(y \le mid\),而单独返回右孩子的条件是 \(x > mid\)
- 单点修改的的时候,分治 \(x=mid\) 归左区间管.
- pushdown之后源节点lazytag要置零
- 区间长度要加一
- 线段树下标不能为0
- 下放后父节点要清零
- 不会就别炫你妈啥比位运算
我全踩过了(怒)
简单线段树
锐评:简单的线段树还不如打树状数组
我们按操作来进行线段树的代码叙述
建树操作
- 建树需要一个原数据数组.
- 如果区间内只有一个点,直接处理后返回
- 区间内有多个点,令 \(mid=(l+r)/2\) , 将原线段分为 \([1,m]\) 和 \([m+1,r]\) 两个区间,分别递归建树.
- 递归结束后,根据左右区间的结果处理当前区间
void build(int id,int l,int r){
t[id].l=l;
t[id].r=r;
if(l==r){
t[id].min=a[l];
return;
}
int mid=(l+r)/2;
build(tol,l,mid);
build(tor,mid+1,r);
t[id].min=min(t[tol].min,t[tor].min);
}
单点修改操作
- 从根节点开始向下查找需要修改的单点区间
- 假如区间不匹配,判断需要修改的区间在 \(mid\) 左边还是右边,然后递归搜索(类似二分). 注意 \(x=mid\) 归左区间管.
- 假如区间匹配,修改值并返回.
- 返回时根据返回的结果处理途径节点.
void change(int id,int x,int value){
if(t[id].l==t[id].r){
t[id].min=value;
return;
}
int mid=(t[id].l+t[id].r)/2;
if(x<=mid){
change(tol,x,value);
}
else{
change(tor,x,value);
}
t[id].min=min(t[tol].min,t[tor].min);
}
区间查询操作
- 从根节点开始向下查找需要查询的区间
- 假如当前区间被目标区间完全包含,直接返回存储结果.
- 若不包含,判断目标区间在 \(mid\) 左边还是右边,若在左边 ( \(y \le mid\) ), 递归返回左区间答案. 否则 ( \(x > mid\) ), 递归返回右区间答案. 假如在左右区间都有,将区间分成 \([l,mid]\) 和 \([mid+1,r]\) 两个部分,分别计算后合并处理.
int find(int id,int x,int y){
if(t[id].l>=x&&t[id].r<=y){
return t[id].min;
}
int mid=(t[id].l+t[id].r)/2;
if(y<=mid){
return find(tol,x,y);
}
if(x>mid){
return find(tor,x,y);
}
return min(find(tol,x,mid),find(tor,mid+1,y));
}
进阶线段树
LazyTag
- LasyTag 并不参与任何结果存储,只是一个用来更新子节点的工具.
- LazyTag 存储的是子节点尚未更新的数目,当需要时,储存在 LazyTag 中的所有未更新的信息将全部下放到子节点, 引导子节点更新.
- 除非走到末梢,否则 LazyTag 不会凭空消失,因为虽然子节点被更新了,但是子节点的子节点还未更新,而未更新的数目恰好就是原 LazyTag 值,所以可以认为下放时 LazyTag 会继承给子节点.
下放
- 子节点的 LazyTag 增加父节点的 LazyTag 值.
- 将未更新的值全部更新. 对区间和来说,未更新的值的大小为区间长度与更新量的乘积.
- 父节点的 LazyTag 清零.
void pushdown(int id){
if(t[id].lazy==0){
return;
}
t[tol].lazy+=t[id].lazy;
t[tor].lazy+=t[id].lazy;
int mid=(t[id].l+t[id].r)/2;
t[tol].sum+=t[id].lazy*(mid-t[tol].l+1);
t[tor].sum+=t[id].lazy*(t[tor].r-mid);
t[id].lazy=0;
}
区间修改
- 区间修改并不是真的修改区间内每一个值,我们采用一种 “不用就不更新” 的思想,将所有更新存入 LazyTag 内.
- 存入 LazyTag 的值只针对其子节点,所以我们仍然需要对当前节点执行赋值操作. 对区间求和来说,具体操作为使该区间的和增加区间长度
- 假如当前区间被目标区间完全包含,直接赋值并更新 LazyTag 然后返回.
- 假如不包含,先下放当前区间的 LazyTag. 然后判断目标区间在左区间还是右区间,若在左边 ( \(t[tol].r\ge l\) ), 递归修改左区间. 否则 ( \(t[tor].l\le r\) ), 递归修改右区间. 假如在左右区间都有,将区间分成修改左区间和修改右区间两个部分,分别计算后合并处理.
void change(int id,int l,int r,int value){
if(t[id].r<=r&&t[id].l>=l){
t[id].sum+=value*(t[id].r-t[id].l+1);
t[id].lazy+=value;
return;
}
pushdown(id);
if(t[tol].r>=l){
change(tol,l,r,value);
}
if(t[tor].l<=r){
change(tor,l,r,value);
}
t[id].sum=t[tol].sum+t[tor].sum;
}
区间查询
- 假如当前区间被目标区间完全包含,直接返回存储值.
- 假如当前区间与目标区间无公共部分,直接返回.
- 否则说明当前区间与目标区间有不完全的公共部分,首先下放当前区间的 LazyTag,然后记录ans, 判断目标区间与左右区间有无公共部分,若有就递归求值,然后返回结果.
int find(int id,int l,int r){
if(t[id].l>=l&&t[id].r<=r){
return t[id].sum;
}
if(t[id].r<l||t[id].l>r){
return 0;
}
pushdown(id);
long long ans=0;
if(t[tol].r>=l){
ans+=find(tol,l,r);
}
if(t[tor].l<=r){
ans+=find(tor,l,r);
}
return ans;
}