原地址:My CSDN Blog 那边还未通过审核,先看这边的吧……
线段树是一个很好的维护区间关系的这样的一个数据结构,但是,很多时候我们可以用更小空间、更快速度(更大尺寸呢、,全景天窗,五菱宏光?)的数据结构来维护一个前缀关系。
上面的这张图是表示的一个有8个叶子节点的线段树,接下去,我们给它进行一个变形:
然后呢,我们把2、4、6、8、……这样的元素推到最顶端的空上边去,想让2表示1~2这段区间,让4表示1~4这段区间,让6表示5~6这段区间,让8表示1~8这段区间。
然后,这张图就变成了这样:
那么,我们知道了2、4、6、8可以表示的区间,诶~那其他的区间(前缀关系)呢!古人(比我老的都是古人!)想到了一种很巧妙的解决的方法,我们来举例说明:
我们想求1~1的时候,是不是就是A[1];
想求1~3的时候,是不是A[3]和A[2]两个这样的关系式,因为A[3]只表示3~3这个区间,如图;
想求1~5的时候,我们就要用A[5]、A[4]这两个关系式了;
求1~6的时候,要用A[6]、A[4]这两个式子了;
1~7呢,是A[7]、A[6]、A[4]这三个关系的统筹了。
从中,我们能否找到什么规律?
首先,我们知道树状数组仍然是跟线段树一样是一棵二叉搜索树(BIT),性质仍然没有改变。那么,我们不妨溯本求源从二进制的角度来思考问题。
我们将这8个数都转换成二进制来看,
1 = (0001)—— Q[1] = A[1];
2 = (0010)—— Q[2] = A[2];
3 = (0011)—— Q[3] = A[3] + A[2];
4 = (0100)—— Q[4] = A[4];
5 = (0101)—— Q[5] = A[5] + A[4];
6 = (0110)—— Q[6] = A[6] + A[4];
7 = (0111)—— Q[7] = A[7] + A[6] + A[4];
8 = (1000)—— Q[8] = A[8];
从中我们是不是可以发现,我们想求1~x的一个前缀的关系,是不是就可以把x看成二进制再进行求解:看到7 = (0111)是不是可以把7看成Q[7] = Q[ (0111) ] = A[ (0111) ] + A[ (0110) ] + A[ (0100) ]这样的一个关系,我们不难发现,对于7是不是每次去除的都是其二进制上的最后一位1,直到为0停止运算,譬如(0111)->(0110)我们去除了最末尾的1,(0110)->(0100)也是去除了最末尾的那个1。
Problem:怎么去除某数最末尾的那个1?
Q1:从0开始跑一遍for(i, 0~31)循环判断到某一位"(x>>i) & 1"是不是为1?——好浪费时间的说~(留给……的时间不多了!)(脱口而出!
Q2:new algorithm?新的算法?不是新的哦~是一个我们平时没有关注到的一个小玩意!
方法一:
如何消除二进制中最后一个位1?利用n &= (n - 1)就可以做到。(手动模拟下下哦,31为二进制好长的说呢QAQ)(雾
方法二:
这时候就要回到C语言的课本知识了,我们知道负数的补码是怎样计算的呢?
举个例子:6 = (0110),那么(-6)=(1001 + 1)=(1010)
也就是先对所有位置取反,然后再"+1"这样的一个过程。那么,好巧喔,是不是发现“x&(-x)”就是我们所要删除的最后一位的1的对应的值?
给个好听的名字吧,就叫lowbit()吧!
这里还有一道还不错的题!lowbit()的「藏起来
#define lowbit(x) ( x&(-x) )
我习惯把它放在头文件里边,因为真的很常用的!
到这里,我们是不是知道了大致上该怎样去查找,那么,更新哩,一开始的树状数组是不是空的?所以,我们需要去更新它!
看到图片,再手动模拟一下给大家看看,我们要更新以下的序号,那么哪几位是需要改变的呢?(我们把图放下来继续看着图)
1(0001)—— A[1]、A[2]、A[4]、A[8];
2(0010)—— A[2]、A[4]、A[8];
3(0011)—— A[3]、A[4]、A[8];
4(0100)—— A[4]、A[8];
5(0101)—— A[5]、A[6]、A[8];
6(0110)—— A[6]、A[8];
7(0111)—— A[7]、A[8];
8(1000)—— A[8];
在这里,我们不难发现,更新的时候与查询相反,我们是去给最低位的1上加上1,然后不断的向前进位,就可以把对应的关系我们存进去。
这里给出一个求前缀和的模板(My Code):
#define lowbit(x) ( x&(-x) )
int A[maxN], N, M; //maxN是根据题目的呢
inline void add(int x, int v) //第x位,给它加上v的权
{
if(!x) return; //没有这个有可能会死循环哦,牢记!!!
while(x <= N)
{
A[x] += v;
x += lowbit(x);
}
}
inline int query(int i) //查询1~i的和
{
int ans = 0;
while(i)
{
ans += A[i];
i -= lowbit(i);
}
return ans;
}
对了,就像Code里面讲的那样,我们更新的时候,如果没有:
if(!x) return;
是有可能会死循环的(有些地方会改变0处的值,我们得想办法去处理它的!)。
为什么会死循环!?是因为lowbit(x)也是0呀。然后就永远永远的加不到N了(雾
好啦。树状数组的基本操作就讲到这里叭,自我感觉良好~(膨胀ing?🎵)
真的没有彩蛋了,别往下看了。
没彩蛋了……
没彩蛋了。
没了哦。。