树状数组

树状数组或二元索引树（英語：Binary Indexed Tree），又以其发明者命名为芬威克樹（英語：Fenwick tree），最早由彼得·M·芬威克（Peter M. Fenwick）于1994年以《A New Data Structure for Cumulative Frequency Tables》为题发表在SOFTWARE PRACTICE AND EXPERIENCE。其初衷是解决数据压缩裡的累积频率（Cumulative Frequency）的计算问题，现多用于高效计算数列的前缀和， 区间和。它可以以 O ( log ⁡ n ) {\displaystyle O(\log n)} 的时间得到任意前缀和 ∑ i = 1 j A [ i ] , 1 <= j <= N {\displaystyle \sum _{i=1}^{j}A[i],1<=j<=N} ，并同时支持在 O ( log ⁡ n ) {\displaystyle O(\log n)} 时间内支持动态单点值的修改。空间复杂度 O ( n ) {\displaystyle O(n)} 。 按照彼得·M·芬威克的说法，正如所有的整数都可以表示成2的幂和，我们也可以把一串序列表示成一系列子序列的和。采用这个想法，我们可将一个前缀和划分成多个子序列的和，而划分的方法与数的2的幂和具有极其相似的方式。一方面，子序列的个数是其二进制表示中1的个数，另一方面，子序列代表的f[i]的个数也是2的幂。 定义一个lowbit函数，返回参数转为二进制后,最后一个1的位置所代表的数值。 例如，lowbit(34)的返回值将是2；而lowbit(12)返回4；lowbit(8)返回8。 将34转为二进制为(0010 0010)2。这里的“最后一个1”指的是从 2 0 {\displaystyle 2^{0}} 位往前数，见到的第一个1，也就是 2 1 {\displaystyle 2^{1}} 位上的1。 程序上，(~i + 1) & i表明了最后一位1的值。 仍然以34为例，~(0010 0010)的结果是1101 1101（221），加1后为1101 1110（222），把0010 0010与1101 1110作AND，得0000 0010（2）。 lowbit的一个简便求法:（C++） int lowbit(int x) { return x&(-x); } 定义一个数组 BIT，用以维护 A {\displaystyle A} 的前缀和，则： B I T i = ∑ j = i − l o w b i t ( i ) + 1 i A j {\displaystyle BIT_{i}=\sum _{j=i-lowbit(i)+1}^{i}A_{j}} 具体能用以下方式实现：（C++） void build() { for (int i = 1; i <= MAX_N; i++) { BIT[i] = A[i - 1]; for (int j = i - 2; j >= i - lowbit(i); j--) BIT[i] += A[j]; } } //注：这里的求和将汇集到非终端结点（D00形式） //BIT中仅非终端结点i值是 第0~i元素的和 //终端结点位置的元素和，将在求和函数中求得 //BIT中的index，比原数组中大1 假设现在要将 A [ i ] {\displaystyle A[i]} 的值增加delta， 那么，需要将 B I T [ i ] {\displaystyle BIT[i]} 覆盖的区间包含 A [ i ] {\displaystyle A[i]} 的值都加上delta， 这个过程可以写成递归，或者普通的循环。 需要计算的次数与数据规模N的二进制位数有关，即这部分的时间复杂度是 O ( log ⁡ N ) {\displaystyle O(\log {N})} 。 修改函数的C++写法： void edit(int i, int delta) { for (int j = i; j <= MAX_N; j += lowbit(j)) BIT[j] += delta; } 假设我们需要计算 ∑ i = 1 k A i {\displaystyle \sum _{i=1}^{k}A_{i}} 的值。 首先，将ans初始化为0，将i初始化为k。 将ans的值加上BIT[i]。 将i的值减去lowbit(i)。 重复步骤2～3，直到i的值变为0。 求和函数的C/C++写法： int sum (int k) { int ans = 0; for (int i = k; i > 0; i -= lowbit(i)) ans += BIT[i]; return ans; } 初始化复杂度最优为： O ( N ) {\displaystyle O(N)} 单次询问复杂度： O ( log ⁡ N ) {\displaystyle O(\log N)} ，其中N为数组大小 单次修改复杂度： O ( log ⁡ N ) {\displaystyle O(\log N)} ，其中N为数组大小 空间复杂度： O ( N ) {\displaystyle O(N)} 树状数组可以通过维护差分数组来处理区间修改和单点查询。具体地，当我们在 [ l , r ] {\displaystyle [l,r]} 增加 d {\displaystyle d} 时只需执行 edit(l,d) 和 edit(r+1,-d) 。查询则与单点修改区间查询相似。 逆序对数是一个数列中在它前面有比它大的个数。如4312的逆序对数是0+1+2+2=5。 可以先把数列中的数按大小顺序转化成 1 {\displaystyle 1} 到 n {\displaystyle n} 的整数（離散化），使得原数列成为一个 1 , 2 , . . . , n {\displaystyle 1,2,...,n} 的排列 P {\displaystyle P} ，创建一个树状数组，用来记录这样一个数组 A {\displaystyle A} （下标从1算起）的前缀和：若排列中的数 i {\displaystyle i} 当前已经出现，则 A [ i ] {\displaystyle A[i]} 的值为 1 {\displaystyle 1} ，否则为 0 {\displaystyle 0} 。初始时数组 A {\displaystyle A} 的值均为 0 {\displaystyle 0} ，从排列中的最後一个数开始遍历，每次在树状数组中查询有多少个数小于当前的数 P [ j ] {\displaystyle P[j]} （即用树状数组查询数组 A {\displaystyle A} 目前 P [ j ] − 1 {\displaystyle P[j]-1} 个数的前缀和）并加入计数器，之后对树状数组执行修改数组 A {\displaystyle A} 第 P [ j ] {\displaystyle P[j]} 个数值加 1 {\displaystyle 1} 的操作。 Peter M. Fenwick. A new data structure for cumulative frequency tables. Software: Practice and Experience. 1994, 24 (3): 327–336 [2015-10-24]. doi:10.1002/spe.4380240306. （原始内容存档于2021-02-25）.  Binary indexed tree-树状数组. [2012-05-09]. （原始内容存档于2019-08-23）.  Binary Indexed Trees. [2012-05-09]. （原始内容存档于2016-06-16）.  TopCoder树状数组教程的译文. [2012-11-18]. （原始内容存档于2013-04-10）.  存档副本. [2012-05-11]. （原始内容存档于2019-11-30）.