一般的树状数组是从1为开始的,因为每次操作是对最低位的1操作,也就是+-lowbit(i) (lowbit(i) = i & -i)。
也可以从0开始,这样每次操作就不是对最低位的1操作了,而是对最低位的0操作,也就是(lowbit(i) = ~i & i + 1)。
所以从0开始的树状数组可以这样写:
void update(int x, int val)
{
for(int i = x; i <= lim; i += ~i & i + 1) tree[i] += val;
}
void query(int x) {//查询0~x的个数
int sum = 0;
for(int i = x; ~i; i -= ~i & i + 1) sum += tree[i];
return sum;
}
让我们来简单做个证明它的正确性。
对query(x)操作,假设x=(1000100100111)2.
第一步sum += tree[1000100100111], 得到的是1000100100000~1000100100111的所有个数
第二步sum += tree[1000100011111], 得到的是1000100000000~1000100011111的所有个数
第三步sum += tree[1000011111111], 得到的是1000000000000~ 1000011111111的所有个数
第四步sum += tree[0111111111111], 得到的是 0000000000000~ 0111111111111的所有个数
因为树状数组是位运算的产物,可以结合二分的位运算实现产生更华丽的应用。
比如这样的二分:
for (int step = 20; step >= 0; -- step) {
lb += 1 << step;
if (lb > n || std::max(c,C[lb]) >= std::min(d,D[lb])) lb -= 1 << step;
else c = std::max(c,C[lb]),d = std::min(d,D[lb]);
}
这在二分下界lb,每次需要得到前缀1 ~ lb的最大值,每次不需要在树状数组上询问一下query(lb),而是在二分的过程中就可以一直更新1~lb的最大值,因为这是位运算实现的二分,每次lb += 1 << step以后,lowbit(lb)等于1 << step,所有只需要在二分过程中更新最大值就行了,这样复杂度就从logn * logn 降低到logn了。
但是有人会注意到,这个只有lowbit(lb) = lb & -lb这种树状数组才满足等于1<<step,没错。但从0开始的树状数组也可以二分,需要加一点技巧,lb初始值设为-1就可以了,这样每次 1 << step 就等于 lowbit(lb)了。
比如:-1 + 1000000 = 0111111 , lowbit(0111111) = 1000000
这个应用可见:http://codeforces.com/contest/689/problem/D
这是lowbit(x) = x & -x 的二分代码:http://codeforces.com/contest/689/submission/18978399
这是lowbit(x) = ~x & x + 1的 二分代码:http://codeforces.com/contest/689/submission/18933132