[OI] 线段树
1.[OI] 树上背包问题2.[OI] 图论3.[OI] 关于最小环和负权环4.[OI] 分层图最短路5.[OI] 关于Eratosthenes筛法的优化思路6.[OI] DP
7.[OI] 线段树
8.[OI] 扫描线9.[OI] KMP10.[OI] 二分图11.[OI] 平衡树12.[OI] pb_ds13.[OI] Kruskal 重构树14.[OI] 模拟退火15.[OI] 可持久化数据结构16.[OI] Testlib17.[OI] 交互 | pipe18.[OI] 二项式期望 DP19.[OI] 整体二分20.[OI] 结构体引用类型转换21.[OI] 猫树22.[OI] 树链剖分23.Borůvka 算法24.线段树分治25.C++ 编译静态链接 (-static)打线段树的时候容易踩的坑
- 数组开四倍
- 建树的时候叶节点赋值之后要return
- 单点修改的时候叶节点赋值之后也要return
- 单点修改的时候,要根据当前mid与搜索位置判断是要修改左子树还是右子树
- 区间查询的时候,边界条件不是
,而是 ,否则在分段搜索更小的区间时会死循环. - 因为我们建树的时候,区间中间点划分是向下取整的,因此我们在区间查询的时候,单独返回左孩子的条件是
,而单独返回右孩子的条件是 - 单点修改的的时候,分治
归左区间管. - pushdown之后源节点lazytag要置零
- 区间长度要加一
- 线段树下标不能为0
- 下放后父节点要清零
- 不会就别炫你妈啥比位运算
我全踩过了(怒)
简单线段树
锐评:简单的线段树还不如打树状数组
我们按操作来进行线段树的代码叙述
建树操作
- 建树需要一个原数据数组.
- 如果区间内只有一个点,直接处理后返回
- 区间内有多个点,令
, 将原线段分为 和 两个区间,分别递归建树. - 递归结束后,根据左右区间的结果处理当前区间
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);
}
单点修改操作
- 从根节点开始向下查找需要修改的单点区间
- 假如区间不匹配,判断需要修改的区间在
左边还是右边,然后递归搜索(类似二分). 注意 归左区间管. - 假如区间匹配,修改值并返回.
- 返回时根据返回的结果处理途径节点.
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);
}
区间查询操作
- 从根节点开始向下查找需要查询的区间
- 假如当前区间被目标区间完全包含,直接返回存储结果.
- 若不包含,判断目标区间在
左边还是右边,若在左边 ( ), 递归返回左区间答案. 否则 ( ), 递归返回右区间答案. 假如在左右区间都有,将区间分成 和 两个部分,分别计算后合并处理.
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. 然后判断目标区间在左区间还是右区间,若在左边 (
), 递归修改左区间. 否则 ( ), 递归修改右区间. 假如在左右区间都有,将区间分成修改左区间和修改右区间两个部分,分别计算后合并处理.
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!