当我们计算一个数组的前缀和时,我们直接从数组开头加到结尾,sum = a[0] + a[1] + ...+ a[n-1].
sum = 0;
for(int i = 0;i < n;i++)
sum += a[i];
但是我们要对数组进行修改,再查询前缀和的时候,需要重新加一遍,时间复杂度为O(n),当n很大的时候效率会变得很低,所以我们引入了树状数组,树状数组可以把查询和点更新都在O(logn)的时间内完成,那么树状数组是怎么实现的呢。
树状数组顾名思义,就是用数组来模拟树形结构
树状数组如图:
由图中可以看到树状数组是一个树形结构,数列的数用数组A[]来存储,同时引入了管理小组C[],管理小组的每个成员存储他的子节点的和,如图:
C[1]:存储A[1];
C[2]:存储C[1],A[2];
C[3]:存储A[3];
C[4]:存储C[2],C[3],C[4]......
树状数组是怎么将前缀和查询降到O(logn)的呢,其实看图可以知道,当我们查询一个节点的前缀和的时候只需要将这个节点左侧的子树的根加起来即可,
sum[4]:左侧没有子树 直接找c[4].sum[4] = c[4];
sum[5]:左侧有一棵子树,其根为c[4],sum[5] = c[4] + c[5]......这样的结构可以把O(n)的复杂度降低,不必每次都从头开始加。
树状数组的点更新其实也很简单,只要将包含该节点的树的树根更新即可,比如上面的树状数组图中,要更新A[3]的值,在树状数组中只要修改包含C[3]的树即可,C[3]:包含于C[4]和C[8],所以只需要修改C[4]和C[8]即可;
树状数组的代码实现:
如何确定树状数组C中存哪个数呢,这里我们介绍一个lowbit操作,这个是根据第i个数的末尾有几个0,C[i]便存前2的i次个数
附上一个特别棒的网站,帮助理解(动画演示代码):
https://visualgo.net/zh/fenwicktree
int lowbit(int x)
{
return (-x) & x;
}
树状数组点更新
void update(int i,int v)
{
for(;i<=n;i+=lowbit(i))
c[i] += v;
}
树状数组前缀和
int sum(int i)
{
int s = 0;
for(;i>0;i-=lowbit(i))
s+=c[i];
return s;
}
树状数组求区间和
int sum1(int l,int r)
{
int s1 = sum(l-1);
int s2 = sum(r);
return s2 - s1;
}