上次我们讲了树状数组,今天我们来讲一下另一种可解决类似问题但是应用范围更广的方法——大名鼎鼎的线段树。
线段树整体运用了分治的思想,把一个区间一分为二,分成的两部分继续分,直到不能再分,最后组成一棵树。通过这棵树,我们可以完成区间的一些操作。它能完成的操作有单点修改、区间查询、求最大最小值等等。那么我们可以先看一下如何构建这棵树:
void build(int root,int l,int r){
tree[root].left=l;
tree[root].right=r;
//如果左区间等于右区间:遇到了叶子节点-->直接赋值
if(l==r){
tree[root].val=a[l];
return;
}
//对于非叶子节点:取到中间值,左右分别递归
int mid=(l+r)>>1;
build(root<<1,l,mid);
build(root<<1|1,mid+1,r);
//回溯时,当前结点的值就是左右儿子的和
tree[root].val=tree[root<<1].val+tree[root<<1|1].val;
}
主函数引用:
build(1,1,n);
配合注释,应该比较容易理解。
查询也比较简单,对于一个节点[L,R]分为几种不同的情况:
l<=L且R<=r,此时树上节点被完全包含在区间[l,r],所以该节点的sum需全部贡献
r<=mid(mid=(L+R)/2)说明[l,r]全部在[L,R]的左半部分,所以该节点的左儿子的sum要贡献(非全部)
l>mid说明[l,r]全部在[L,R]的右半部分,所以该节点的右儿子的sum要贡献
除开以上3种情况,剩下的只可能是左右都有,所以该节点的左右儿子的sum都要贡献
代码
long long query(int root,int l,int r){
if(tree[root].left>=l&&tree[root].right<=r){
return tree[root].val;
}
int mid=(tree[root].left+tree[root].right)>>1;
long long ans=0;
if(l<=mid){
ans=query(root<<1,l,r);
}
if(r>mid){
ans=query(root<<1|1,l,r);
}
return ans;
}
最后是修改,也很简单,只需要找到要修改的叶子节点,修改后不断向上递归修改即可:
void update(int root,int n,int v){ //将a[n]加上v
if(tree[root].left==tree[root].right){
tree[root].val+=v;
return;
}
int mid=(tree[root].left+tree[root].right)>>1;
if(n<=mid){
update(root<<1,n,v);
}
else{
update(root<<1|1,n,v);
}
tree[root].val=tree[root<<1].val+tree[root<<1|1].val;
}
以上是本篇博客的全部内容,后续还会继续讲有关线段树的内容,深入学习线段树。感谢观看