树状数组详解

为什么要使用树状数组

比如说,我这里有一组数1, 2, 3, 2, …, k。我想知道第i到第j的和\(\mathop \sum \limits_{n = i}^j v[i]\)是多少?

朴素算法:

for (int k = 0; k < n; k++)
    if (k >= i && k <= j) ans += v[k];

类似这种的写法,虽然在某些点值改变时也依然可以计算(我们称这种问题为动态问题),但复杂度最高到O(n),实在难以接受。

树状数组是通过前缀和思想,用来完成单点更新和区间查询的数据结构。它比之线段树,所用空间更小,速度更快,而且编程的复杂难度也大大减小。和ST相比,它可以动态更新数据。

前缀和

我们可以把查询区间和问题转化为前缀和问题。
前缀和是指:v[1]~v[i]的和,记作sum(i)。

如果我们能够计算s和t的前缀和,用sum(t)−sum(s−1)可以得到区间[s, t]的和。

而树状数组正是一个支持求前缀和的数据结构。

什么是树状数组

(如果你学过线段树,可以先从最后“树状数组的缺陷”看起,树状数组的灵感来源脉络会更加清晰)
Binary Indexed Tree
我们依据二进制的思想,让”为1的最低位”在第一位的数(即奇数),储存长度为1的区间和(即v[i])。最低位在第二位的数,储存长度为2的区间和(v[i-1]+v[i])。这样,value数组储存了大大小小、长度为2^n 的区间和。

每一个长条形的块表示这个节点覆盖的区间。value[index]等于区间节点值之和。

这样做有什么好处呢?
1. 任意前缀和Sum(i)可以通过加运算得到。
2. 任意v[i]都可以通过减运算得到。

算法设计

注:后面出现的节点编号都是二进制数
- 如何求“最低位在哪一位”

举一个具体的运算过程:

操作数值表达式
给定I10010i
i取反01101~i
加一01110~i+1
和i求交00011&

通过上面4步就可以求一个数的最低位在哪一位了!

我们先由此定义一个lowbit函数:lowbit(i) = 保留i最低位的1和更低位的0。
比如lowbit(100010) = 10

那么我们就可以具体推导出一些性质了:
1. 区间长度为1<<(lowbit(i)-1)
2. 区间是[i-lowbit(i)+1, i]

在具体程序中,lowbit(i) = i & -i。因为计算机里的整数采用补码表示,因此-x是x取反后+1的结果。

而对于节点编号为0110的节点,它恰好由一个长度为4的区间和一个长度为2的区间组成。第一个lowbit(i)表示它自身所覆盖的区间,而第二次lowbit(i-lowbit(i))表示它的前一个区间。同理,我们可以递归地求得所有它前面的区间。

BIT的求和

BIT sum
为了求得sum(i),将i通过二进制分解,不断让i = i - lowbit(i)。对于每个求得的i(包括初始i)求和value[i]就是sum(i)

BIT的更新

和线段树的更新不同,BIT的更新指的是v[i]加上a。所以需要让x[i] = a时,需要先-v[i]再+a。

对于更新操作,因为我们在节点储存部分和,所以当一个点更新后,覆盖这个点的父节点的值也要更新。我们的问题在于如何找到它的父节点,父节点的父节点等等,一直更新到顶部。

根据之前的铺垫,我们对BIT已经有了一定的认识,我们可以感性地猜想,一个节点的父节点t = i + lowbit(i)。想法是基于:通过进位使得覆盖的区间变大,而且可以证明, t - lowbit(t) + 1 <= i < t。i一定会被覆盖。
BIT update
同理递归地更新上去就可以了。

事实上,我们更新的点都是2的幂,即形如1000的。所以每次加上lowbit,让低位全为0就可以。

具体算法

  1. 求和
ll b[MAX_N+1];

ll sum(int i) {//[1, i]
    ll s = 0;
    while (i > 0) {
        s += b[i];
        i -= i & -i;
    }
    return s;
}
  1. 更新
void add(int i, ll v) {
    while (i <= N) {
        b[i] += v;
        i += i & -i;
    }
}

树状数组的缺陷

其实谈到树状数组,有一个数据结构不能不提——线段树。
Interval Tree
线段树的每个节点都储存了数据,但在计算和和更新值时,我们是不会用到右儿子的!如上图所示。
BIT structure
于是我们删除了线段树的所有右儿子,形成了树状数组。

但是这里有一个前提条件,也是树状数组的缺陷:节点的数据必须是可加减,而且要满足a + b = c, c -a = b
比如集合运算,是不能通过父节点和左子节点 来计算右子节点的。

题目总结

  • LA 4329 Ping pong
  • HDU 5975 Aninteresting game

参考文献

I. 《挑战程序设计竞赛 第二版》
II. 《算法竞赛 入门经典 训练指南》

这里是我的blog:有更多算法分享。排版可能也会更好看一点=v=
https://endlesslethe.com/binary-indexed-tree-tutorial.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值