树状数组的问题模型
首先我们搞明白树状数组是用来干嘛的,现在有一个这样的问题:有一个数组a
,下标从0
到n-1
,现在给你w
次修改,q
次查询,修改的话是修改数组中某一个元素的值;查询的话是查询数组中任意一个区间的和,w + q < 500000
。
这个问题很常见,首先分析下朴素做法的时间复杂度,修改是O(1)O(1)的时间复杂度,而查询的话是O(n)O(n)的复杂度,总体时间复杂度为O(qn)O(qn);可能你会想到前缀和来优化这个查询,我们也来分析下,查询的话是O(1)O(1)的复杂度,而修改的时候修改一个点,那么在之后的所有前缀和都要更新,所以修改的时间复杂度是O(n)O(n),总体时间复杂度还是O(qn)O(qn)。
可以发现,两种做法中,要么查询是O(1)O(1),修改是O(n)O(n);要么修改是O(1)O(1),查询是O(n)O(n)。那么就有没有一种做法可以综合一下这两种朴素做法,然后整体时间复杂度可以降一个数量级呢?有的,对,就是树状数组。
lowbit
函数
这里我们先不管树状数组这种数据结构到底是什么,先来了解下lowbit
这个函数,你也先不要问这个函数到底在树状数组中有什么用;
顾名思义,lowbit
这个函数的功能就是求某一个数的二进制表示中最低的一位1
,举个例子,x = 6
,它的二进制为110
,那么lowbit(x)
就返回2
,因为最后一位1
表示2
。
那么怎么求lowbit
呢?
把这个数的二进制写出来,然后从右向左找到第一个1
(这个1
就是我们要求的结果,但是现在表示不出来,后来的操作就是让这个1
能表示出来),这个1
不要动和这个1
右边的二进制不变,左边的二进制依次取反,这样就求出的一个数的补码,说这个方法主要是让我们理解一个负数的补码在二进制上的特征,然后我们把这个负数对应的正数与该负数与运算一下,由于这个1
的左边的二进制与正数的原码对应的部分是相反的,所以相与一定都为0,;由于这个1
和这个1
右边的二进制都是不变的,因此,相与后还是原来的样子,故,这样搞出来的结果就是lowbit(x)
的结果。
int lowbit(int x) {
return (-x)&x;
}
求和
ll sum(int k) {
ll s = 0;
while(k > 0) {
s += c[k];
k -= lowbit(k);
}
return s;
}
更新
void update(int i, int x) {
while(i <= n) {
c[i] += x;
i += lowbit(i);
}
}