【算法#3】树状数组&二叉索引树
其实是数据结构。
智推连续几天给我推树状数组的模板,还放在第一位……
对着蓝书的图看了好几天才看懂,树状数组的另外一个名字是二叉索引树,指通过把一个数组抽象的变形成树状的以求得到树形数据结构的效果。有人说是线段树的阉割版,我不太清楚,树状数组应该是不支持区间修改加速的。
首先我们需要理解lowbit的概念,它指的是一个数转成二进制后位数最低的那个1表示的值。它具有一个特殊的性质但是为什么具有这个性质是无须证明也不用了解的。
然后我们画一个图,在一定范围内按lowbit的大小从上向下逐层分布,一层中按编号排序。不难发现这是棵二叉树。(见蓝书)我们让每个节点水平延伸lowbit(它的)-1长度的条,包括该节点在内的长条表示它覆盖的区间内的所有点的和或者其他什么性质。
并不一定非要纠结于这个图,然后我们发现lowbit的一个特殊性质就是一个区间的编号减去它的lowbit等于不包含这个区间的且在该区间前面的第一个(最靠右)的区间。一个区间的编号加上它的lowbit等于包含该区间的且最小(最靠左)的区间的编号。很玄学,对吧。
我们知道了lowbit的概念和它的性质,那么怎么求lowbit呢?我们知道,计算机中采用补码,也就是取一个数的负数等于这个数取反加一,以100为例:
01100100(100)
10011011(-99)//是-101还是-99?
10011100(-100)
00000100(对-100和100按位取与)
所以\(lowbit(x)=x&-x\)。更玄学了对吧。
对于查询区间和,在当前区间编号非零的情况下使结果加上当前区间,再去往上一个区间(减去lowbit)。可以得到以x为起点的前缀和。
对于单点修改,在当前区间编号小于等于n的时候使当前区间修改值。
至于预处理……对于每个元素,进行n次add修改操作就好了。板子贴在这里。
int lowbit(int x){
return x&-x;
}
int sum(int x){
int res=0;
while(x){
res+=c[x];
x-=lowbit(x);
}
return res;
}
void add(int x,int d){
while(x<=n){
c[x]+=d;
x+=lowbit(x);
}
}
上面介绍了对于基本的单点修改,区间查询的做法(其实仍不完整,最值问题不好搞。)
然后我们试着对树状数组进行修改以使其的功能更接近线段树。
首先我们需要对基本的差分的概念进行叙述。
设:有数组\(\texttt{a[maxn]}\)保存每个数据原本的值,然后我们定义另外一个数组\(\text{d[maxn],d[0]=0}\)且对于\(d[i](1\leq i\leq maxn)\)有\(d[i]=a[i]-a[i-1]\)。
这个d保存着每个数的差分。假设我们对一个区间内的数整体加上某个值x,易得区间内的数的差分值是不变的,除了区间开头的差分值增加了x,对于一个一般的差分数组,修改的复杂度是O(1)的,查询是O(n)的。
对于这个差分数组建立一个树状数组,我们就能使之具有树状数组的性质,即nlogn预处理,logn区间修改,logn单点查值。
改动很小并且不易写错,只需要在构造的时候将add的参数从\(\text{a[i]}\)改为\(\text{a[i]-a[i-1]}\)其他不变。区间修改区间查询的可能也是在差分的基础上,再维护每一个点的值的树状数组?好像不太对,搞懂了我会来补充的。