在处理RMQ问题的方法中我最不熟练并且无力的就是树状数组,因为只要碰到这种问题基本上都打了线段树。——线段树都会了要树状数组干嘛,树状数组能求区间最大嘛?树状数组能区间修改嘛?树状数组能持久化嘛?——然后这些都是可以的....给被我冤屈的树状数组正个名吧...
树状数组的存储方式:
树状数组可以看做...用二进制末个1的位置分层的线段树?...爱线段树爱得太深,所以什么都以线段树为基准...
说到树状数组就不能不放这张图,老人家书上的树状数组我觉得更加清晰,但是拿不下来...这就不能怪我了
因为二进制的性质太好了,就像倍增一样我们每隔一段就可以"腾出"一个点来充当两个点父亲----末尾1的位数往前进一.
我们让每个点存它直到左儿子lowbit=1的和.
我记得我是看过整个树状数组的体系的证明的,但是我已经忘光了(雾
区间求和:
在树状数组的结构基础之上,我们可以很方便地求出前缀和,如果我们要求1-n的前缀和则从n开始一直减去lowbit并且加起来即可.相当于从那个点一直往左上爬,边爬边加上当前点的值.
为什么是正确的呢,首先当前点左上方的点记录的和一定不包括当前点-----毕竟维护的是前缀和,如何证明它覆盖了整个区间呢.
不管了!反正就是这样的!!!证明一遍太麻烦!!
那么我们要求一段区间的和就只要求两段减一减就好了.
单点修改:
跟区间求和相反,单点修改时更新数据要往右上走,因为右上的顶点才是包含它的.
我们只要走到根,边走边改就好了.
区间修改(加加减减):
类似求和的思想,修改l,r我们把1,r加上a. 1,l-1减去a就好了---------不过还是要一个一个点改.
区间最值:
这个区间最值有点像平衡树搞最值-----旋出一个完全只有那段序列的子树,然后更改即可.
我们注意到,一个点与它父亲结点之间的点维护的是这个序列,我们在更新的时候只要更新他们即可.因为这个点的减去它的lowbit是它的父亲,我们只要在这个范围之内遍历结点即可.
修改的代码:
void change(int r) { c[r]=num[r]; for(int i=1; i<lowbit(r); i<<=1) c[r]=max(c[r], c[r-i]); }
求值时同理啊.一模一样,不断找父亲(来自ioi大爷的代码)
int getk(int l, int r) { int ret=num[r]; while(l<=r) { ret=max(ret, num[r]); for(--r; r-l>=lowbit(r); r-=lowbit(r)) ret=max(ret, c[r]); } return ret; }
先这样吧,据说可以可持久化,不过我觉得也没卵用啊...把这个可持久化还不如搞线段树...