详细讲解了线段树。
线段树
前言
看了OI Wiki的线段树,最近几天琢磨明白了,分享一下我的理解。
线段树能做什么
线段树是一个能快速求区间和、区间最大值、区间最小值的数据结构。
它也可以单点修改、区间修改。
而上述操作的复杂度都是O(logN),这无疑是迅速的。
它是如何实现的
建树
一开始我们有一个区间[1,n],代表一个n长度的数组的所有值。
然后我们把他劈成两半,[1,⌊21+n⌋]和[⌊21+n⌋+1,n],把这两个区间抽象为[1,n]的子节点。然后重复这个过程,把区间[a,b]劈成[a,⌊2a+b⌋]和[⌊2a+b⌋+1,b]。
这样整个数组就变成了一颗二叉树。

我们可以通过递归来完成线段树的建树,以区间求和为例,代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| int raw[N+1];
int seg[4*N+1];
void build(int left,int right,int index){ if(left==right){ seg[index]=a[left]; return; } int mid=left+(right-left)/2; build(left,mid,2*index); build(mid+1,right,2*index+1); seg[index]=seg[2*index]+seg[2*index+1]; } int main(){ build(1o }
|
容易发现建图的复杂度为O(n)
区间查询

我们现在已经能完成这些节点上区间的查询了。
不难发现,我们只需要把一些线段树上区间求并集,就可以得到任意区间。
我们只要找离根节点的最短路的距离最低的、且是查询区间的子集的节点,就是那几个构成查询区间的线段树上区间。
这个部分比较简单,我就直接给代码了,还是以求和为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int raw[N+1]; int seg[4*N]; int get(int left,int right,int query_left,int query_right,int index){ if(left<=query_left&&right<=query_right){ return seg[index]; } int mid=left+(right-left)/2; int ret=0; if(query_left<=mid) ret+=get(left,mid,query_left,mid,2*index); if(query_right>=mid+1) ret+=get(mid+1,right,mid+1,query_right,2*index+1); return ret; } int main(){ build(1,n,1); get(1,n,a,b,1); }
|
需要注意一下query_left和query_right在分治的过程中的变动,这是一个很重要的细节。
区间查询的复杂度为O(logN)
区间修改与懒标记
我已信奉鸽子教,,,
所以我先不写
致谢
十分感谢OI Wiki,这个项目使我学到了很多知识。