遇到困难睡大觉|

园龄:粉丝:关注:

【复健】树状数组2

树状数组复健2

展开目录

注:因为习惯和省事问题,下文的 lowbit 代表 lowerbit,但后者也会时而出现。

为什么重写

image

↑您不觉得我这玩意写得逻辑不通吗而且抽象

关于树状数组

是什么

并不是一种树形结构。

image

↑按照我的理解这玩意差不多长成这个样,类似砖头

通过一些分散的小区间来储存数据,每个数据可能会同时出现在多个区间内。包含同一个数据相邻两个区间之间间隔 lowerbit(x),即截留下这个数的最末位 1及其之后的 0 所形成的数。

为什么

快(这不废话吗

对于一个普通数组,单点修改的复杂度是 O(1),区间查询的复杂度是 O(n);对于一个前缀和数组,区间查询的复杂度是 O(1),但单点修改的复杂度是 O(n).

其中 n 为区间长度。

树状数组单点修改和区间查询的复杂度都是 O(logn).

另外一点是,如果实在没时间打线段树,树状数组的码量会小一点。

关于 lowbit 运算

e.g.

x=(11001)2 是一个二进制奇数,我们知道 x=(10011001)2, 即把符号位变成 1

x 的补码是原码,x 的补码就是它的反码 +1, 而:

image

所以可得 x 的补码为 (11010)2, x 的补码为 (11100111)2, x&(x)=(1)2, 就取到了 x 的最后一位 1.

因为正数的补码和原码相同,负数的补码和对应的正数相比,除了最后一位 1 及其以后的 0, 其它的全部取反,按位与结果为 0. 这样就获取到末位 1 了。

偶数比如 1000010010000, 显然后者在取反后会变为 11101111, 实际上是与 10000 完全相反(包括符号位)的,所以 +1 后我们就会看到 x 的补码为 11110000,从而得到 10000 最末位的 1.


代码实现

因为是砖头(?)式排列 法,可以参考在砖墙上涂鸦(?

修改一种砖头的纹路需要把所有包含这种纹路的砖头都修改,查询类比前缀和。

其实不看成砖头理解起来也没有任何麻烦(

注:默认原始数组为 a,树状数组为 bit,数组大小为 n.

单点修改

因为区间间隔是 lowbit(x),所以循环的步长就是这么多。

void update1(int i, int x) {
	for(int pos = i; pos <= n; pos += lowbit(pos)) bit[i] += x;
}

区间修改

只需要把单点修改丢进 for 循环即可:

void update2(int l, int r, int x) {
	for(int i = l; i <= r; ++i) update1(i, x);
}

求前 n 项和

第一个区间不一定包含第 n 项,对其执行 lowbit 操作所得到的区间也不一定包含。

但是反过来,先查询第 n 个区间是可以的。第 n 个区间一定包含第 n 项。

为什么?

再看看这行代码:for(int pos = i; pos <= n; pos += lowbit(pos)),在初始化时我们必然使用 update1 操作来初始化每个编号为 i 的点,而这个操作是从第 i 个区间开始的。

int query1(int i) {
	int ans = 0;
	for(int pos = i; pos; pos -= lowbit(x)) ans += bit[pos];
	return ans;
}

区间查询

类比前缀和。

int query2(int l, int r) {
	return query1(r) - query1(l - 1); 
}

如果想要单点查询, 直接 query(i, i) 即可。

例题们

咕咕咕

本文作者:Kiichi

本文链接:https://www.cnblogs.com/Kiichi/p/17956597/BITfujian2

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   _Kiichi  阅读(73)  评论(13编辑  收藏  举报
   
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起