对于线段树,主要是编程比较麻烦,而树状数组编程比较容易,对于一些问题,使用树状数组会比较方便。
c[i]表示的是区间长度为 2的(i的后连续的0的个数)幂次:2^k(k为最后的连续的0的个数)
原数组a[]
hint:c数组和a数组都是从下标为1开始的
1)对于树状数组某一个区间的
修改操作:
假设需要修改a[k];
首先,我们只要知道哪些c里面记录的包含了a[k]这个元素。如果包括的话,它也需要被修改。由于树状数组的结构定义的特点,我们能知道。
例如:假设a的第di=3个数被修改了,那么包含di的区间的c[i],di的末尾有连续k个0。此时,i=di+2^k。然后依次递推下去,将所有包含了di个元素的c[i]都进行相应的改变。算法如下。
// 求x所表示的区间的长度
inline int lowbit(int x)
{
return x&-x ;
}
void change(int di,int x)
{
while(di<=Maxa)
{
c[di] += x ;
di += lowbit(di);
}
}
这里其实很不好理解,感觉我每次看树状数组都会重新的理解一下这个change函数的意思。
最好的方法是举例子:如下举2个例
ex1:di=2,首先看图di=2(10),加上2的话,正好是di=4,然后可以知道这里,有点想在将1进行左移位运算。
ex2:di=3,首先看图di=3(11),加上1的话,正好是di=4。这里都正好是它们的祖先节点,不知道这里说祖先节点是否合适。
对于,所有的节点,它的所有的祖先节点记录了孩子节点的信息,所以,当孩子的信息进行了变化时,那么他们的祖先都得进行相应的变化。
这里其实,我也不是十分的清楚为什么是向上加lowbit(di)得到的区间就是包含当前的区间。让我再想想吧。。。
这里有一点的是,当第一次加上了一个lowbit(di)的时候,di就变成了2的幂次了。
树状数组真的很有意思,感觉很是神奇~~
2)询问某一个区间的操作:
int query(int di)
{
int ret=0;
while(di>0)
{
ret+=c[di];
di-=lowbit(di);
}
return ret;
}
这里用di=6作为例子来举例很好。
通过计算,可以知道c[di]记录的区间的大小是2。而我们需要知道的是区间大小是6,所以还需要继续计算剩下的6-2个区间。
然后,一直这样循环下去。
ex2:
当di=4时,可知,c[di]记录的区间大小正好是4,那么直接就ok了。
这里对于query的操作还是很容易懂的。
一维的树状数组就简单的介绍到这里
///
接下来的是 二维的树状数组:
int c[MAXNUM][MAXNUM];
int Lowbit(int m)
{
return m&(-m);
}
int query2(int i,int j)
{
int tempj,sum=0;
while(i>0)
{
tempj = j;
while(tempj>0)
{
sum += c[i][tempj];
tempj -= Lowbit(tempj);
}
i -= Lowbit(i);
}
return sum;
}
void change2(int i,int j,int m,int MAXNUM)
{
int tempj;
while(i<=MAXNUM)
{
tempj = j;
while(tempj<=MAXNUM)
{
c[i][tempj] += m;
tempj += Lowbit(tempj);
}
i += Lowbit(i);
}
}
其实二维的也十分的简单,至少思想也很简单,可以这么理解,对于想要知道区间[1,1]到[i,j]的矩形区域里的总和,这时,我们最简单的方法是一个个的累加这个矩形区域里的所有值,此时,一般你会这么进行累加,一行一行的进行。而此时,如果我们假设,我们可以在一行累加的时候不用累加所有的数,而只需要累加一些连续的区域,这里使用树状数组可以。
而在累加所有的行的时候也是如此,我们对 一些连续的行的区域进行累加。
感觉讲的好绕啊,自己理解吧,最简单的是自己脑子运行一下query2(2,2),你肯定会懂的,呵呵。
这种方法能很好的实现矩形区域的累和。二维区域。
到这里,就简单的学完了基本的树状数组的相关内容了,剩下的就是去使用了与提高认识了