线段树详解
1.引例
假如现在有一个问题,让你修改一段区间的数,并询问任意一段区间的和,你该怎么办?
方法一:暴力修改加查询---------------不可以,时间复杂度太高了
方法二:利用前缀和查询---------------也不可以,修改怎么办呢?
方法三:线段树维护区间---------------可以~~~
2.前置知识
3.基本思路
线段树就是把树转换成一个区间,并用二分的思想层层递归下去,直到当前的信息能够返回答案(或已经递归到了叶节点)
而线段树能够解决的问题主要是单点查询,单点修改,区间查询,区间修改
下面演示一下线段树具体怎么操作
如果有一个区间[1-10]
那么第一次二分的区间即为[1-5][6-10],不能返回值,继续递归
第二次:[1-3][4-5],[6-8][9-10],不能返回值,继续递归
第三次:[1-2],[3],[4],[5],[6-7],[8],[9],[10],现在区间3,4,8,9,10都可以返回
第四次:[1],[2],[6],[7],全部可以返回
以上就是线段树的操作过程
4.代码实现
让我们先了解线段树的基本操作
1.建树(build)
2.上传(pushup)
3.下传(pushdown)
4.修改(modify)
5.查询(query)
这就是线段树的五种基本操作
(1)建树(build)
建树就是把一棵树转换成一段区间,还是运用了二分思想(不要问我为什么,线段树很玄学)
当递归到了叶结点的话,把已知的信息存进树(结构体)里 否则,继续二分
以下就是建树的代码
void build(int u,int l,int r)
{
if(l==r)tr[u]={l,r,a[r]};
else
{
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid);build(u<<1|1,mid+1,r);
pushup(u);
}
}
注意:u<<1和u<<1|1表示的是当前节点的子节点,当然用2u,和2u+1也是没问题的
(2)上传(pushup)
上传很好理解,就是把子节点的信息上传到父节点
同时,上传操作不是固定的,它因你的操作而改变
比如说,你想求区间和,代码就是tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
区间最大值就是tr[u].Max=max(tr[u<<1].Max,tr[u<<1|1].Max);
同理,最小值就是tr[u].Min=min(tr[u<<1].Min,tr[u<<1|1].Min);
(3)下传(pushdown)
下传和上传正好相反,下传是把信息传到子结点上
同理,下传也是不固定的
在这里演示一下区间和的代码
void pushdown(int u)
{
auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
if(root.add)
{
left.add+=root.add,left.sum+=(ll)(left.r-left.l+1)*root.add;
right.add+=root.add,right.sum+=(ll)(right.r-right.l+1)*root.add;
root.add=0;
}
}
这里的sum是从L到R区间的和,所以要乘上父节点的add
不要忘记最后把root.add还原成0
(4)修改(modify)
修改的操作分两种情况
①当前可以返回值----------直接返回
②不可以返回值 -------------继续递归
于是我们的代码就成了:
void modify(int u,int l,int r,int d)
{
if(tr[u].l>=l&&tr[u].r<=r)
{
tr[u].add+=d;
tr[u].sum+=(ll)(tr[u].r-tr[u].l+1)*d;
}
else
{
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid)modify(u<<1,l,r,d);
if(r>mid)modify(u<<1|1,l,r,d);
pushup(u);
}
}
(5)查询(query)
明白了修改,理解查询操作就很简单了 也是两种情况
①当前可以返回结果----------直接返回
②不可以返回结果--------------分成两段来求解
下面给的代码以求区间和为例
ll query(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r)return tr[u].sum;
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
ll sum=0;
if(l<=mid)sum+=query(u<<1,l,r);
if(r>mid)sum+=query(u<<1|1,l,r);
return sum;
}
4.实践
学会了线段树之后,需要多刷题来巩固
这里推荐几道题
2.统计和
3.【模板】ST 表
最后一个温馨提示: