前缀和数组:
初始化:O(n)时间复杂度,顺序扫描原数组即可
查询区间和:O(1)时间复杂度,S[j]-S[i]即为原数组i到j的区间和
单点修改:O(n)时间复杂度,需要修改S[i]~S[n]所有值,单点修改效率低
lowbit函数
定义:lowbit(i)代表i这个数字,二进制表示的最后一位1的位权
例如:
lowbit(8)=(1000)2 =8
lowbit(6)=(110)2=4
lowbit(12)=(1100)2=4
lowbit(7)=(111)2=1
计算公式:lowbit(x)=x&(-x)
对于这个公式的理解:
我们知道,对于一个数的负数的二进制表示使用补码,补码就等于对这个数取反+1
以二进制数11010为例:11010的补码为00101,加1后为00110,两者相与便是最低位的1
其实很好理解,补码和原码必然相反,所以原码有0的部位补码全是1,补码再+1之后由于进位那么最末尾的1和原码
最右边的1一定是同一个位置(当遇到第一个1的时候补码此位为0,由于前面会进一位,所以此位会变为1)
树状数组
定义:c[i]表示前lowbit(i)项的和
例如:lowbit(8)=8 c[8]=a[1]+a[2]+…..+a[8]
lowbit(6)=2 c[6]=a[5]+a[6]
单点更新操作

由上图可知,修改A[5]的值需要更新C[5],C[6],C[8],因为C[5],C[6],C[8]覆盖了A[5],C[5]好理解,毕竟更新了自己原数组上的值,但是要怎么知道更新的是C[6],C[8]呢?
先上代码:
void add(int i,int x) {while(i <= n) {c[i] += x;i += lowbit(i);}}
也就是说更新需要从i开始找一直到n,每次i更新为i+lowbit(i)
lowbit(i)是i二进制中最后一个1的位权,i+lowbit(i)表示在i的基础上至少扩大了两倍,也就是说C[i + lowbit(i)]会包含下标为i的原数组的值,因此也需要更新。
区间查询

区间查询比较好理解,i每次需要往前走的步长是i-lowbit(i),这个根据定义就可以得出.
比如前6项和,则C[6]需要往前找,C[6]表示前lowbit(6)=2项和,那么还需要加上i-lowbit(6)=6-2=4,由于4-lowbit(4)=0,因此S[6]=C[6] + C[4],如果i - lowbit(i)还大于0,则需要继续寻找下一项C[i]。
上代码:
int query(int i) {int sum = 0;while(i > 0) {sum += c[i];i -= lowbit(i);}return sum;}
根据前缀和数组恢复原数组:A[i]=query(i)-query(i-1).
树状数组完整代码:
class FenwickTree {int n;int[] c;int lowbit(int x) {return x & (-x);}//初始化FenwickTree(int n) {this.n = n;c = new int[n + 1];}//单点更新void add(int i,int x) {while(i <= n) {c[i] += x;i += lowbit(i);}}//前缀和int query(int i) {int sum = 0;while(i > 0) {sum += c[i];i -= lowbit(i);}return sum;}}
