树状数组是一种数据结构,非常适用于数列中点以及区间的查询或更新,例如求区间的和,可以直接扫描一遍这个区间,复杂度是 O ( n ) O(n) O(n),如果要操作 M M M次呢?那么最坏情况下复杂度变成了 O ( M ∗ N ) O(M*N) O(M∗N),当数据规模十分庞大的时候是很划不来的,如果使用树状数组,复杂度会变成 O ( M ∗ l o g ( n ) ) O(M*log(n)) O(M∗log(n))
首先来看一张图
现在有 8 8 8个数,存在数组 A A A中,同时有另外一个数组 C C C,表示我们要维护的树状数组, C C C中的每个点维护了一段或一个点的信息,那么数组A分别对应图上树的8个叶子结点,而图上标记数字的,表示数组 C C C的下标,那么根据以上关系可得
C [ 1 ] = A [ 1 ] C[1] = A[1] C[1]=A[1] C [ 2 ] = A [ 1 ] + A [ 2 ] C[2] = A[1]+A[2] C[2]=A[1]+A[2] C [ 3 ] = A [ 3 ] C[3] = A[3] C[3]=A[3] C [ 4 ] = A [ 1 ] + A [ 2 ] + A [ 3 ] + A [ 4 ] C[4] = A[1] + A[2] + A[3] + A[4] C[4]=A[1]+A[2]+A[3]+A[4] C [ 5 ] = A [ 5 ] C[5] = A[5] C[5]=A[5] C [ 6 ] = A [ 5 ] + A [ 6 ] C[6] = A[5] + A[6] C[6]=A[5]+A[6] C [ 7 ] = A [ 7 ] C[7] = A[7] C[7]=A[7] C [ 8 ] = A [ 1 ] + A [ 2 ] + A [ 3 ] + A [ 4 ] + A [ 5 ] + A [ 6 ] + A [ 7 ] + A [ 8 ] C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8] C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8]
那么数组A和C之间一定存在某种关系,让我们在对数组A进行区间或点的操作时,可以转换到数组C上,通过树的关系来减少运算次数,怎么操作呢? 首先观察数组 C C C和 A A A的关系 不难,当下标为奇数的时候, C [ i ] = A [ i ] C[i] = A[i] C[i]=A[i],当下标为2的幂数的时候 C [ i ] = ∑ j = 1 i A j C[i] = \sum\limits_{j=1}^iA_j C[i]=j=1∑iAj(即前缀和),当下标为偶数的时候, C [ i ] = A [ i − 1 ] + A [ i ] C[i] = A[i-1] + A[i] C[i]=A[i−1]+A[i],如果把每一个 C C C中加进去的 A A A的数量,叫做这一段的长度,那么对于每一个 C [ i ] C[i] C[i]有什么方法可以快速获得段长吗? 那就要考虑二进制 对于下标 1 − 8 1 -8 1−8,二进制的变化如下(假设只考虑一个字节) 00000001 00000001 00000001 (1) 00000010 00000010 00000010 (2) 00000011 00000011 00000011 (1) 00000100 00000100 00000100 (3) 00000101 00000101 00000101 (1) 00000110 00000110 00000110 (2) 00000111 00000111 00000111 (1) 00001000 00001000 00001000 (4) 其中后面()内部的数字,表示从右往左二进制1第一次出现的位置,很显然,这就是要找的段长,从 C [ 1 ] − C [ 8 ] C[1]-C[8] C[1]−C[8]段长分别为 1    2    1    3    1    2    1    4 1\,\,2\,\,1\,\,3\,\,1\,\,2\,\,1\,\,4 12131214,那么这个段长就是取二进制的从右往左第一个1的位置,这个操作叫 l o w b i t lowbit lowbit
lowbit(x) = (x&(-x))//因为负数是通过取反+1来实现的 /* 例如 8 00001000 11110111 + 1 = 11111000 相与得00001000 转换为10进制为8,说明C[8]维护的段长是8 */通过 l o w b i t lowbit lowbit就可以快速获取段长,有了这个操作就可以很方便实现操作了
代码如下
//单点修改、区间查询 #define lowbit(x) (x&(-x)) #define ll long long #define M 100005 ll n, m, c[M]; inline ll read() { //快读 ll x = 0; bool flag = 0; char c = getchar(); while(c < '0' || c > '9'){if(c == '-') flag = 1; c = getchar();} while(c >= '0' && c <= '9'){x = x*10+c-48; c = getchar();} if(flag) x = -x; return x; } void change(int x, int d) { //修改所有包含x的信息 for(int i = x; i <= n; i+=lowbit(i)) c[i] += d; } ll query(int x) { //查询,求[1,x]的和,如果要求和[l,r],则应该query(r)-query(l-1) if(x == 0) return 0; ll ret = 0; for(int i = x; i; i -= lowbit(i)) ret += c[i]; return ret; } int main() { n = read(), m = read(); //n个数,m次操作 for(int i=1;i<=n;i++) change(i,read());//边读,边更新树状数组 }