您的位置:首页 > 其它

一维 + 二维树状数组 + 单点更新 + 区间更新 详解

2015-08-08 00:13 447 查看
树状数组详解:

假设一维数组为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]而前六个之和即为C[4] + C[6],如此,此处说明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]
}
return ret;
}


如果是二维的树状数组的话,心里思考一下,是不是感觉很眼熟哦!

其实他们的原理是一样的:

设二维数组为:

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(j)){
ret += C[i][j];
}
}
return ret;
}


这就是二维树状数组的成型,那么三维以及以上呢,想来大家都已经秒懂了,是的,没错,就是在最外一层加个循环跟一维变为二维原理一样

而对于区间增加以及减少做个简单的解答:

对于树状数组,只能够提供区间修改(增加或者是减少d),最后单点求值

首先讲一下所谓的区间修改,他的本质其实还是单点修改,因为树状数组基本提供的功能就是两个

一个是单点修改,一个是区间求值,所以区间修改以及单点求值的本质还是树状数组的两个基本功能

首先是区间修改对应的树状数组的功能是单点修改,通过修改单个点的值来修改整个区间

而怎样通过单个点的修改来表示修改了整个区间呢?

首先我们要理解一个思维就是区间修改的思维方式与之前的树状数组的表达方式不相同了,

首先说明C[i]求解与树状数组肯定没有什么区别,但是sum[i]表示的却是i这个点的值,

为什么,因为此时的A[1],A[2]....A[m]表示不是他的值,在以往的树状数组中,他都是表示

位于数组1位置的值,位于数组2位置的值....位于数组m位置的值,

但是此时,他们只是一个值的一部分,此刻,有些人想不明白了,什么叫做一个值得一部分

其实是这样的,原本A[m]的值被分解了,分解成了A[m] = A[m - lowbit(m)] + A[m - lowbit(m) - lowbit(m - lowbit(m))] + .....

如此,所谓的C[m]表示的只是前辍和的一部分,如图解:




如此,如果要区间修改,单点求值的话,前提必须是初始条件能够满足构成前缀和

我们要改变一个区间[a , b]的值,让他们加一的话先是[a .... N] + 1为什么,这就是往后更新

然后是[b .... N] - 1即可,如此通过上图可以知道B[i]表示i点的值,他的值是通过A[1 . . . i]的前缀和得来的

如此我们只要在C[i]处+ 1,那么通过前辍和可以知道,后面的数已经在这个数加一的基础都加了个一,他们的值已经表示为了一个区间内变化

当然这种变化是从i....N这个区间都变化了,所以还要讲[b ,,,, N]变回来就可以了

如此可以明白通过求和可以得到i这个点的值(B[i])因为求和的含义就是代表i点这个数的值.



博客中有个简单的例子:/article/8817309.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: