线段树详解
今天复习一下线段树。
1.基本结构
基本结构是这样的:
然后我们就可以建立一棵线段树。
根节点编号为1,之后第二层的第一个儿子编号为2,第二个儿子是3...
总结一句话,线段树是一颗二叉树,并且每个儿子节点都把父节点分成均匀的两个区间;若父节点的编号是x,那么两个子节点的编号就分别是和
2.建树
建树怎么建呢?
从根节点开始,依次遍历它的两个儿子,然后如果发现l=r,那么此节点就是个叶子节点。
这时候,有人可能会问了,线段树可以干啥呢?
它的作用有很多,这里我举一个例子,它可以维护区间的最大值,并且支持修改操作。
那么我们就可以轻松建树。设每一个节点分别代表l~r这个区间的最大值,那么我们从根节点开始,如果发现左指针=右指针(它是个叶子节点),那么这个节点的最大值就是它的左指针在输入的数组中的值。
一个父节点的最大值就是它两个儿子之间的最大值。
void build(int l, int r, int v) { // 对 [l,r] 这个区间建立线段树,当前节点的编号为 p ,tree 为线段树,a 为输入数组
if(l==r){
tree[v]=a[l];
return;
}
int mid=(l+r)/2;
build(l,mid,v*2);// 递归对左区间建树
build(mid+1,r,v*2+1);// 递归对右区间建树
tree[v]=max(tree[v*2],tree[v*2+1]);//更新父节点
}
3.区间查询操作
还是刚刚的例子,如果我们要查询一个区间[l,r]间的最大值,如何用线段树维护呢?
首先,我们从根节点开始,我们设[s,t]为现在查找到的区间。然后我们发现,如果[s,t]在[l,r]里边(换句话说s ≥ l ,t ≤ r)。
我们发现如果当前的区间[s,t]的左区间与[l,r]有交集(就是包括,但不完全包括),那么我们就要查询左区间。
同理,如果它和右区间有交集,则查询右区间。
最终我们将查询到的结果返回即可。
void qry(int i,int s,int t,int l,int r){
if(s<l||t>r) return ; //若这个区间和查询的区间没有交集,则直接返回
if(l>=x&&r<=y){ans=max(ans,tree[i]); return ;} //如果直接包含了,那么直接取这个区间的最大值
int mid=(l+r)/2;
qry(i*2,l,mid);
qry(i*2+1,mid+1,r);
}
4.区间/单点修改操作
<1>.单点修改
单点修改就很好修改了,上下跑一边就可以了。
具体的话就是从根节点向下遍历,直到找到这个节点的位置。然后再从这个节点一路向上修改,把它的父亲节点更新。
void add(int i,int l,int r){
if(l==r){//如果是单点的话就直接修改
tree[i]=y;
return ;
}
int mid=(l+r)/2;
if(x<=mid) add(i*2,l,mid);
else add(i*2+1,mid+1,r);
tree[i].maxn=max(tree[i*2].maxn,tree[i*2+1].maxn);//更新父节点
}
<2>.区间修改
区间修改则有点小复杂,它是把[l,r]这个区间都加上了一个数,然后再取最大值
这里我们需要引入一个叫做[懒惰标记](Lazy Tag)的东东
这个东东可以用来干什么呢?
首先,我们分析发现,如果对于[l,r]这个需要修改的区间,我们一个一个去修改的话,那么我们的时间复杂度就比暴力还多了log 2 n 倍,那么很明显,这个操作十分的不划算。
那么这个时候我们就要使用懒惰标记了!
何为懒惰标记呢?
看一张图片:
那么如图所示,我们就把[1,2]这个区间打上懒惰标记,并且把它的懒惰标记通过down操作传给它的儿子。
我们发现,其实对于一个打上了懒惰标记的点,我们只需要把它的两个儿子加上这个懒惰标记的值,然后再把它的两个儿子的懒惰标记加上它的懒惰标记就可以了。最后别忘了清空这个节点的懒惰标记
记住我们的区间查询操作也要改!
void down(int x){//lazy 为懒惰标记数组,tree 为线段树
if(!lazy[x]) return ;
lazy[x*2]+=lazy[x];
lazy[x*2+1]+=lazy[x];//将它的懒惰标记传给它的儿子
a[x*2].maxn+=lazy[x];
a[x*2+1].maxn+=lazy[x];//让它的儿子也加上它的懒惰标记
lazy[x]=0;//清空它的懒惰标记
}
void add(int i,int l,int r,int v,int x,int y){//x,y 为我们需要修改的区间,l,r为我们现在遍历到的区间,i为当前编号,v为我们要加的值
if(x<=l&&y>=r){//如果需要修改的区间直接包括了当前遍历到的区间
a[i].maxn+=v;
lazy[i]+=v;//打上懒惰标记
return ;
}
down(i);
int mid=(l+r)/2;
if(x<=mid) add(i*2,l,mid);
if(y>mid) add(i*2+1,mid+1,r);
a[i].maxn=max(a[i*2].maxn,a[i*2+1].maxn);
}
void qry(int i,int l,int r,int x,int y){//我们的查询操作也要变!!!!
if(r<x||l>y) return ;//如果这俩区间完全不相交,直接返回
if(l>=x&&r<=y){ans=max(ans,a[i].maxn); return ;}//如果直接包含,那么直接取max返回
down(i);//记得这里也要down!!!
int mid=(l+r)/2;
if(x<=mid) qry(i*2,l,mid);
if(y>mid) qry(i*2+1,mid+1,r);
}
The End
2022.5.2
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具