树状数组(小白)

树状数组(小白)

树状数组(BIT)是一种利用树的2进制特征进行检索的树状结构。树状结构是一种奇妙的数据结构,不仅非常高效,而且代码十分简洁(比线段树的代码要短且更易理解,但是可以解决的问题也是有限的,没有线段树那么广泛)
这这个大圆就相当于线段树能解决的问题,小圆就是树状数组能解决的问题,一般来说能用树状数组解决的就优先用树状数组

树状数组的概念

树状数组就是用来 动态的求前缀和·(代码的时间复杂度在log n之内)
一般来说就是两个操作 :1.单点修改 2.区间查询(通俗来说就是给某个位置上的数加上一个数;求前缀和)
当然平时做题的时候也不止这两种操作 比如有1.区间修改 2.单点查询 或者 1.区间修改 2.区间查询
(这几种操作本质上还是用树状数组解决 只不过是需要用到差分 将它转化为最初的两个操作)

下面开始举一个例子

长度为n的数列{a1,a2.....,an},进行以下操作。
(1) 修改元素add(k,x);ak(ak为数列中第k个数)加上x;2)求和sum(x):x<=n,sum=a1+a2.....+ax;
那么区间和ai+.....+aj=sum(J)-sum(i-1).

这个程序很好写,用循环加或者前缀和,复杂度是O(n).但是,如果n很大的话,这样做的效率会非常低。所以我们需要掌握树状数组。

先看代码:

int lowbit(int x){
return x&-x;
}
void add(int x,int v){  //更新数组tree[]. ax=ax+d,修改跟ax有关的tree[]
for(int i=x;i <=n;i +=lowbit(i)) tree[i] +=v;
}
int sum(int x){ //求和:sum=a1+a2....+ax
int res=0;
for(int i=x;i >0;i -=lowbit(i)) res +=tree[i];
return res;
}

add()和sum()的复杂度都是O(log2 n).(打不出来这个东西 😂)
在理解上述代码的之前 先看我下面这张图
在这里插入图片描述
这张图就是解释了add()构建tree[]数组 tree[1]=a[1] tree[2] =a[1] +a[2]; tree[3] =a[3] tree[4] =a[1]+a[2]+a[3] … 连线就代表此时tree[x]的值为那些连线位置的和
可是为什么是这些值呢
这就要看这个神奇的操作了 lowbit(x) .lowbit(x)=x&-x,功能是找到x的二进制的最后一个1.其原理是利用负数的补码表示,补码是原码取反加一。例如x=6=00000110,-x=x补=11111010,
那么lowbit(x) =x&-x=10(2进制)=2;
仔细观察就会发现 让补码与原码一一对应 以x的2进制的最后一个1为中心 原码和补码左边的部分是刚好相反的 所以在对x&-x操作时 前面的部分都为0 0&0 当然也为0 所以x&-x=10(2进制的)=2;
是不是很妙
lowbit(x)有什么用呢?
在求和计算和tree[]数组的更新都可以通过lowbit()完成。
借助tree[]数组求sum,例如:
sum[8] =tree[8];
sum[7]=tree[7]+tree[6]+tree[4];
sum[9]=tree[9] +tree[8];
那如何得到上面的关系呢
对tree[]的查找可以通过lowbit(x)实现。例如sum[7]=tree[7]+tree[6]+tree[4];
首先从7开始,加上tree[7] ;
然后7 - lowbit(7)(值为1) =6 再加上tree[6] ;
然后6 - lowbit(6)(值为2) =4 再加上tree[4]
最后4 - lowbit(4) (值为4) =0,结束;

关于tree[ ]数组的更新
当你改变a[x]的值 加个3 tree[]数组的值也应当变化 这个也使用了lowbit(x)
例如 当你改变a[6]的值后
对于tree[]数组来说肯定是6以后的位置收到影响 所以
tree[6] +=3; 然后 6 +lowbit(6) =8;
tree[8] +=3; 然后8 +=lowbit(8) =16;
以此类推 直到tree[n];
所以add() 也是用的这种方法 来初始化tree[]数组

forint i;i<=n ;i ++add(i,a[i]);
这样就是对tree[]数组进行初始化

*这里需要注意一下树状数组的下标起始一定是从1开始 如果题目下标有0的话 可以进行加1处理

说了这么多 应该都懂了树状数组了吧
接下来就开始 实战演练了 帮助你巩固 加强理解

这是树状数组的一道模板题 直接套就可以了(https://www.luogu.com.cn/problem/P3374)

然后关于树状数组较难的题 我也还在学习中😂 以后遇到会加进去的
那就先这样吧 本人小白 如有错误欢迎各位大佬指正
//南昌理工学院ACM集训队

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
树状数组(Fenwick Tree)是一种用于快速维护数组前缀和的数据结构。它可以在 $O(\log n)$ 的时间内完成单点修改和前缀查询操作,比线段树更加简洁高效。 下面是 Java 实现的树状数组详解: 首先,在 Java 中我们需要使用数组来表示树状数组,如下: ``` int[] tree; ``` 接着,我们需要实现两个基本操作:单点修改和前缀查询。 单点修改的实现如下: ``` void update(int index, int value) { while (index < tree.length) { tree[index] += value; index += index & -index; } } ``` 该函数的参数 `index` 表示要修改的位置,`value` 表示修改的值。在函数内部,我们使用了一个 `while` 循环不断向上更新树状数组中相应的节点,直到到达根节点为止。具体来说,我们首先将 `tree[index]` 加上 `value`,然后将 `index` 加上其最后一位为 1 的二进制数,这样就可以更新其父节点了。例如,当 `index` 为 6 时,其二进制表示为 110,最后一位为 2^1,加上后变为 111,即 7,这样就可以更新节点 7 了。 前缀查询的实现如下: ``` int query(int index) { int sum = 0; while (index > 0) { sum += tree[index]; index -= index & -index; } return sum; } ``` 该函数的参数 `index` 表示要查询的前缀的结束位置,即查询 $[1, index]$ 的和。在函数内部,我们同样使用了一个 `while` 循环不断向前查询树状数组中相应的节点,直到到达 0 为止。具体来说,我们首先将 `sum` 加上 `tree[index]`,然后将 `index` 减去其最后一位为 1 的二进制数,这样就可以查询其前一个节点了。例如,当 `index` 为 6 时,其二进制表示为 110,最后一位为 2^1,减去后变为 100,即 4,这样就可以查询节点 4 的值了。 最后,我们还需要初始化树状数组,将其全部置为 0。初始化的实现如下: ``` void init(int[] nums) { tree = new int[nums.length + 1]; for (int i = 1; i <= nums.length; i++) { update(i, nums[i - 1]); } } ``` 该函数的参数 `nums` 表示初始数组的值。在函数内部,我们首先创建一个长度为 `nums.length + 1` 的数组 `tree`,然后逐个将 `nums` 中的元素插入到树状数组中。具体来说,我们调用 `update(i, nums[i - 1])` 来将 `nums[i - 1]` 插入到树状数组的第 `i` 个位置。 到此为止,我们就完成了树状数组的实现。可以看到,树状数组的代码比线段树要简洁很多,而且效率也更高。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值