[OI] 线段树

打线段树的时候容易踩的坑

  1. 数组开四倍
  2. 建树的时候叶节点赋值之后要return
  3. 单点修改的时候叶节点赋值之后也要return
  4. 单点修改的时候,要根据当前mid与搜索位置判断是要修改左子树还是右子树
  5. 区间查询的时候,边界条件不是 $ t[id].l=x \And\And t[id].r=y $,而是 $ t[id].l \ge x \And\And t[id].r \le y $,否则在分段搜索更小的区间时会死循环.
  6. 因为我们建树的时候,区间中间点划分是向下取整的,因此我们在区间查询的时候,单独返回左孩子的条件是 \(y \le mid\),而单独返回右孩子的条件是 \(x > mid\)
  7. 单点修改的的时候,分治 \(x=mid\) 归左区间管.
  8. pushdown之后源节点lazytag要置零
  9. 区间长度要加一
  10. 线段树下标不能为0
  11. 下放后父节点要清零
  12. 不会就别炫你妈啥比位运算

我全踩过了(怒)

简单线段树

锐评:简单的线段树还不如打树状数组

我们按操作来进行线段树的代码叙述

建树操作

  • 建树需要一个原数据数组.
  • 如果区间内只有一个点,直接处理后返回
  • 区间内有多个点,令 \(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;
}
posted @ 2024-02-19 09:11  HaneDaniko  阅读(29)  评论(1编辑  收藏  举报