树状数组详解:
假设一维数组为A[i](i=1,2,...n),则与它对应的树状数组C[i](i=1,2,...n)是这样定义的:
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
.................
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
................
如图可知:
为奇数的时候他是代表他本身,而为偶数的时候则是代表着自己以及属于它管辖区域的和。
(1)C[x] 展开以后有多少项?由下面公式计算:
int lowbit(int x) { //计算 C[x] 展开的项数 return x & (-x); }
得到了项数后x - lowbit(x),得到的即使前一区域的x
举个例子:
如果x = 6, x - lowbit(x) == 4 即为C[4]如此,此处说明x & (-x)的作用是求x转换为二进制时最后一个1的位置在哪里,至于为什么,这就是树状数组的神奇之处,一个非常牛逼的规律
然后更新单点的模板代码就成型:
void add(int pos, int c){ while(pos <= n){ C[pos] += c; pos += lowbit(pos);//转移到他的父亲节点,如果这个点更新C[4],那么下一个更新的点就是C[8],相当于他的父亲 } }
求和代码也已经成型:
int sum(int pos){ int ret = 0; while(pos > 0){ ret += C[pos]; pos -= lowbit(pos);//当你要计算1..6的和时,结果即为C[4] + C[6] } }
如果是二维的树状数组的话,心里思考一下,是不是感觉很眼熟哦!
其实他们的原理是一样的:
设二维数组为:
a[][]={{a11,a12,a13,a14,a15,a16,a17,a18},
{a21,a22,a23,a24,a25,a26,a27,a28},
{a31,a32,a33,a34,a35,a36,a37,a38},
{a41,a42,a43,a44,a45,a46,a47,a48}};
那么C[1][1] = a11,C[1][2] = a11 + a12;
如此当C[1][i]...C[1][j]时跟一维的树状数组是没有什么区别的
那么C[2][1] = a11 + a21,C[2][2] = a11 + a12 + a21 + a21,如此可以发现
其实C[2][i].....C[2][j],就是C[1][],C[2][],单独的两个一维树状数组同一位置的值合并在一起
而C[3][1] = a31,C[3][2] = a31 + a32......
而C[4][1] = a11 + a21 + a31 + a41,C[4][2] = a11 + a12 + a21 + a22 + a31 + a32 + a41 + a42
有没有发现,如果单独把二维中的第一个维度拿出来A[1][m] + A[2][m] = C[2][m],A[3][m] = C[3][m],
是不是也和一维的数组一样,所以二维数组的规律就是,不管是横坐标还是纵坐标,将他们单独拿出来,他们
都符合x += lowbit(x),属于它的父亲节点.
如此二维数组的单点更新代码如下:
void add(int x, int y, int c){ //如果我改变了C[x][y]这个点,那么接下来C[x][y += lowbit(y)]当做一维数组的话都是要改变一个c的 //接着我们的纵坐标也是要改变的C[x += lowbit(x)][y]也是要改变的,应为他们都包含了C[x][y]这个集合 for(int i = x;i < n;i += lowbit(i)){ for(int j = y; j < n;j += lowbit(j)){ C[i][j] += c; } } }
那么求和代码就更加好办了
int sum(int x,int y){ int ret = 0; for(int i = x; i > 0; i -= lowbit(i)){ for(int j = y;j > 0;j -= lowbit(i)){ ret += C[i][j]; } } return ret; }
这就是二维树状数组的成型,那么三维以及以上呢,想来大家都已经秒懂了,是的,没错,就是在最外一层加个循环跟一维变为二维原理一样
而对于区间增加以及减少依旧很简答
比如举个例子,我要将[x, y]区间增加d,那么可以先将1....y加上d,然后1.....x减去d
对于二维树状数组的区间更新也是如此
版权声明:本文为博主原创文章,未经博主允许不得转载。