关于lowbit运算
lowbit是找到数字x的二进制的最后一个一代表的数字, 例如lowbit(4)=lowbit((2)100)=100=4;
lowbit(6)=lowbit((2)110)=10=2;
lowbit(7)=lowbit((2)111)=1=1;
给定一个整数x,下面这段代码可以计算出区间[1,x]分成的O(log x)个小区间。
while(x>0){
printf("[%d,%d]\n",x-(x&-x)+1,x);
x-=x&-x;
}
树状数组
树状数组遍是一种数据结构,其基本用途是维护序列的前缀和。对于给定的序列a,我们建立一个数组c,其中c[x]保存序列a的区间[x-lowbit(x)+1,x]中所有数据的和,(可以说是序列的后lowbit(x)个数的和).
事实上,数组c可以看作一个如下图所示的树形结构,满足性质如下:
1.c[x]储存的是以他为根的所有叶节点的和。
2.所有内部节点c[x]的自建点个数等于lowbit(x)的位数
3.除树根外,每个内部节点c[x]的父节点是c[x+lowbit(x)]/
4.树的深度等于O(logN)。
如果N不是二的整次幂,那么树状数组就是一个具有相同性质的森林。
树状数组的操作
树状数组的操作主要有两个,第一个是查询前缀和,即序列a第1-x的数的和,
如下
int ask(int x){
int ans=0;
for(:x;x-=x&-a){
ans+= c[x];
}
return ans;
}
第二个操作是单点增加,即给第x个数加上y,只有节点c[x],和c[x]的祖先需要改动,至多有O(log (N))个,所以可以在log(N)的时间里操作完成。
void add(int x,int y){
for(;x<=N;x+=x&-x)c[x]+=y;
}
在执行所有操作之前,我们需要对树状数组进行初始化操作,针对原始序列a构造树状数组。
为了简便,开始的方法是:建立一个全为零的数组c,然后进行add,完成对序列的添加,时间复杂度为O(NlogN).
更高效的算法是:自小到大考虑剋个节点x,借助lowbit运算扫描子节点并求和。