树状数组
一、适用范围
- 树状数组是一个查询和修改复杂度都为 (log(n)) 的数据结构,常常用于查询任意区间的所有元素之和。
- 与前缀和的区别是支持动态修改, (log(n)) 的时间进行修改,(log(n)) 查询。
- 支持如下操作:
- 单点修改区间查询
- 区间修改单点查询
- 区间修改区间查询
二、算法原理
- 树状数组较好的利用了二进制。它的每个节点的值代表的是自己和前面一些连续元素的和。至于到底是前面哪些元素,这就由这个节点的下标决定。
- 设节点的编号为 (i) ,那么:
-
即可以推导出:
C[1] = A[1] # lowbit(1)个元素之和 C[2] = C[1] + A[2] = A[1] + A[2] # lowbit(2)个元素之和 C[3] = A[3] # lowbit(3)个元素之和 C[4] = C[2] + C[3] +A[4] = A[1] + A[2] + A[3] + A[4] # lowbit(4)个元素之和 C[5] = A[5] C[6] = C[5] + A[6] = A[5] + A[6] C[7] = A[7] C[8] = C[4] + C[6] + C[7] + A[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8]
-
显然一个节点并不一定是代表自己前面所有元素的和。只有满足 (2^n) 这样的数才代表自己前面所有元素的和。
-
理解 (lowbit) 函数
-
原码:如果机器字长为 (n),那么一个数的原码就是用一个 (n) 位的二进制数,其中最高位为符号位:正数为 (0),负数为 (1)。剩下的 (n-1) 位表示该数的绝对值。
-
反码:知道了原码,那么你只需要具备区分 (0) 跟 (1) 的能力就可以轻松求出反码,为什么呢?因为反码就是在原码的基础上,符号位不变其他位按位取反(就是 (0) 变 (1),(1) 变 (0))就可以了。
-
补码也非常的简单,就是在反码的基础上按照正常的加法运算加 (1) 。正数的补码就是其本身。负数的补码是在其原码的基础上符号位不变,其余各位取反,最后 (+1),即取反 (+1)
-
$lowbit(x)=x&-x $ :表示截取 (x) 二进制最右边的 (1) 所表示的值,可以写成函数或宏定义
-
注意宏定义是括号,因为宏名只是起到一个替代作用,不加括号在运算时优先级会出问题
//1. 宏定义,注意括号,不建议这样写,容易产生歧义 #define lowbit(x) ((x) & -(x)) //2. 函数写法,推荐写法: int lowbit(int x){return x & -x;}
-
三、 树状数组的操作
-
(update) 更新操作
-
因为树状数组 (c[x]) 维护的是一个或若干个连续数之和,当我们修改了 (a[x]) 之后,(xsim n) 前缀和均发生了变化,所以除了(c[x]) 需要修改之外 (x) 的祖先节点也必须修改而 (x) 的父亲节点为 (x+lowbit(x)),我们叫向上更新。
-
把序列中第 (i) 个数增加 (x),(sum[i]sim sum[n]) 均增加了 (x) ,所以我们只需把这个增量往上更新即可。如果,把 (a[i]) 修改成 (x),则我们向上更新 (a[i]) 的增量:(x-a[i])。
//1. a[id] 增加 x while写法 void updata(int id,int x){ while(id<=n){//向上更新,更新到n为止 c[id]+=x; id+=lowbit(id); } } //2. a[id] 修改成 x for写法 void updata(int id,int x){//或者传递参数是x=x-a[id],此时跟第一种写法一样 for(int i=id;i<=n;i+=lowbit(i)) c[i]+=x-a[id]; }
-
-
(getsum) 查询操作
-
因为树状数组维护的是一个能够动态修改的前缀和,所以可以在 (log(n)) 的效率下求出前 (n) 项和(sum[i]) 。
-
如果 (i=2^j (j=0,1,..n)), 此时最简单,显然有:(sum[i]=c[i]) ,如果 (i) 是其他的情况呢?
- (sum[5]=c[5]+c[4] (4=5-lowbit(5)))
- (sum[15]=c[15]+c[14]+c[12]+c[8] (14=15-lowbit(15),12=14-lowbit(14),...))
-
显然,想要求出前 (i) 项前缀和 (sum[i]) ,只需沿着当前节点向下累加直到节点编号为 (2^j) 为止。我们叫向下求和。
int getsum(int id){ int tot=0; for(int i=id;i>0;i-=lowbit(i)) tot+=c[i]; return tot; }
-