浅谈线段树
来源:互联网 发布:帝国cms仿站视频下载 编辑:程序博客网 时间:2024/06/06 06:53
注意的:
本文中,特需要注意的用了粗体标注,阅读时可以额外注意.
下面步入正题
线段树
可以说是迈向高级数据结构的必掌握的一个数据结构,其所涉及的范围很广,可以衍生出很多分支,灵活地套在一些题目上,使题目“码量”以及“错误率”增加.
下面我们来详细讲解一下有关线段树的基本知识,尝试深度地理解线段树.
重要的:
我们每学习一种数据结构,必然有其精华所在,也就是我们为什么要学某个数据结构,它有什么优点?又有什么不足的地方,其实说到线段树的优点,就两个字——快、广.
虽然说线段树的常数比树状数组要大许多,但是只要树状数组能做的,线段树基本都能做,反过来就不一样了.
广,就是它涉及的题目范围类型较广,由其衍生出的问题有很多,可以说,掌握了线段树就等同于掌握了一个利器,有助于思考问题时的方向,以及了解出题人的思路.
知道优点,再想想,它为什么会快?为什么涉及范围广?
如果对线段树有了解的,可以直接翻到
①写法&风格
线段树的风格并不是完全一致的。每一个人写线段树都有很不一样的写法,我现在是最普通的写法,(即将
②支持的操作
线段树至少支持以下几个操作:
1、对于一堆数,
2、在某个区间里
3、单点修改
4、区间修改
5、求一段区间和
对应的过程:
一、
二、
三、
四、
五、
③线段树的核心思想——二分
看如上的一颗线段树,很明显可以看出这是一颗“二分树”,为什么是“二分树”,从每个节点所表示的区间范围以及它的左儿子右儿子表示的区间范围中可以明显看出.
④储存方式
那么,对于一颗线段树的每个节点,固然有它所储存的位置,即其对应的编号.
我们可以这么编号,至上到下,从左到右,按照先纵后横的顺序依次编号,即
很明显也可以看出,对于一颗线段树,每一个节点
那么对于每一个编号为
那么,显然有状态:
⑤具体操作
当完全领悟上面四个步骤时,接下来的一切都会变得异常简单,注意一些处理的细节即可.
操作一、maketree 过程——根据一堆数建一颗线段树
首先可以肯定一点,在建树的过程,是每次把一个节点的左儿子和右儿子中的较小值赋为这个节点的值。
假设现在我们求最小值,那么对于maketree过程,如何操作?
例:
这里的叶子节点:49 87 24 67 93 39 66 31 13 53,就是我们输入的数。
显然de——
我们如果要构造一棵线段树,肯定要把一颗线段树当中的叶子节点赋值,之后才能像上面所说的把一个非叶子节点的值赋为其左儿子右儿子当中的较小值。
建树第一步:
递归建线段树的第一步可以理解为:
一步一步往当前非叶子节点的节点的左儿子和右儿子递归,直到
建树第二步:
递归建线段树的第二步可以理解为:
直接把当前非叶子节点的节点赋值为其左儿子或右儿子当中的较小值。
其实这种递归赋值的思路与非递归构造线段树的思路是差不多的.
非递归思路就是把已经确定好的节点数先算出来,其实就是省去第一步,直接赋完值之后,再去赋值非叶子结点的值。但这种方法,其实并没有优化多少时间,因为建树基本上只会建一次。
procedure maketree(x,st,en:longint);var mid:longint;begin if st=en then f[x]:=a[st] else begin mid:=(st+en) shr 1; maketree(x shl 1,st,mid); maketree(x shl 1+1,mid+1,en); f[x]:=max(f[x shl 1],f[x shl 1+1]); end;end;
操作二、find 过程——寻找最大值/最小值的过程
这个操作其实稍微有点麻烦,但其实只要弄懂了线段树的本质,这个也完全可以掌握.
假设我现在要在
这时需要注意一个问题,也是一开始打线段树容易被坑的地方,就是我们递归左子树时,只要
其实,很多东西都是由 一种思想+无数技巧 组成,线段树的中心思想就是二分,通过不停分治,找出子问题的值,最后得到原问题的值.
思考:那么线段树为什么快呢?
原来,我们在查找一个区间
procedure find(x,st,en,l,r:longint);var mid:longint;begin if (st=l) and (en=r) then ans:=max(ans,f[x]) else begin mid:=(st+en) shr 1; if r<=mid then find(x shl 1,st,mid,l,r) else if l>mid then find(x shl 1+1,mid+1,en,l,r) else begin find(x shl 1,st,mid,l,mid); find(x shl 1+1,mid+1,en,mid+1,r); end; end;end;
操作三、Modify ——单点更新.
顾名思义,也就是把一个数更改成另一个数,或者加上某一个数所更改某一个数的值的操作.
有人会问,为什么不直接用上一个操作,其实是因为那个常数有点大了…
procedure modify(x,l,r:longint);var mid:longint;begin if l=r then f[x]:=yy else begin mid:=(l+r) shr 1; if mid>xx then modify(x shl 1,l,mid) else modify(x shl 1+1,mid+1,r); f[x]:=max(f[x shl 1],f[x shl 1+1]); end;end;
操作四、update ——区间修改。
顾名思义,就是把一个区间里所有的数都加上或减少一个数的操作.
实现这个操作,如果我们用单点修改的方法执行这个操作,则易证更改的选择必定超过了
我们可以仿照用之前区间查找的方法,在找到一个相等的
实际上,为了解决这种漏洞,我们采用了标记的方法:
例:
我们现在对对应的编号
易证时间复杂度依然是
注意:当我们区间修改时,有时候并不会把子树一次全部赋值,那么当我查询时依然会出错,所以,当存在区间修改时,查询的时候也要进行与区间修改相同的操作——复制标记(可以参考标程)
procedure update(x,st,en,l,r,t:longint);var mid:longint;begin if (st=l) and (en=r) then begin f[x]:=f[x]+t; g[x]:=g[x]+t; //标记 end else begin inc(f[x*2],g[x]); inc(f[x*2+1],g[x]); inc(g[x*2],g[x]); inc(g[x*2+1],g[x]); g[x]:=0; //即复制标记 mid:=(st+en) div 2; if r<=mid then update(x*2,st,mid,l,r,t) else if l>mid then update(x*2+1,mid+1,en,l,r,t) else begin update(x*2,st,mid,l,mid,t); update(x*2+1,mid+1,en,mid+1,r,t); end; f[x]:=max(f[x*2],f[x*2+1]); end;end;
五、很明显求一个区间的和是很容易想到前缀和的,
很明显这个操作是非常容易实现的,与之前的
现在比较下三个过程的时间复杂度:明显——
由时间复杂度可以看出,对于一个序列,如果只查找区间里面的一个最大值/最小值或这个区间的和,线段树并没有什么优点,但如果查找的数多了,修改的次数多了,则线段树的优势就可以体现出来了。
欲做题:
https://jzoj.net/senior/#main/show/1537
总结题:
https://jzoj.net/senior/#main/show/3512
https://jzoj.net/senior/#main/show/3901
https://jzoj.net/senior/#main/show/4064
https://jzoj.net/senior/#main/show/4063
https://jzoj.net/senior/#main/show/1435
https://jzoj.net/senior/#main/show/1347
- 浅谈线段树
- 浅谈ZKW线段树
- 浅谈 zkw 线段树
- 浅谈线段树
- 线段树浅谈
- 浅谈线段树
- 浅谈线段树标志下放
- 浅谈线段树 Segment Tree
- 浅谈线段树(Segment Tree)
- 【线段树】浅谈区间问题(1)
- 【线段树】浅谈区间问题(2)
- 【线段树】浅谈区间问题3
- BZOJ2957 浅谈线段树的另类用法
- HDU 4366 浅谈DFS序+线段树
- BZOJ 2243 浅谈树链剖分+线段树
- 浅谈二维中的树状数组与线段树
- BZOJ 4034浅谈树链剖分及线段树维护
- CodeForces 438D 浅谈区间取模线段树
- 进程和线程的区别
- linux find命令
- 蓝桥杯 算法训练 纪念品分组
- mongodb单机搭建
- 安卓通过ContentResolver添加联系人
- 浅谈线段树
- ffmpeg 转换x264到Fragmented MP4
- 如何联系博主
- MS SQL Server Management Studio2016(SSMS2016)安装失败 拒绝访问(0x80070005)
- 将svn中的部分功能集成到Unity中
- 全志A33添加开机启动脚本
- 抽象类的作用
- android 通知栏NotificationManager及自定义通知栏Notification的使用
- C++学习笔记(1):命名空间与头文件