# 树状数组 ### 一、树状数组介绍 **树状数组** 是一种数据结构,它可以 **快速计算前缀和** 并 **支持动态维护**。与普通的前缀和数组相比,树状数组的 **优势** 在于它能够在$O(logn)$的时间复杂度内 **更新单个元素的值**,同时仍然能够在$O(logn)$的时间复杂度内 **计算前缀和**。这使得树状数组在 **处理动态数据** 时非常高效: #### 与普通前缀和区别 - **树状数组**: 较快的修改时间$O(log_2N)$+较快的查询时间$O(log_2N)$ - **普通前缀和**:较慢的修改时间$O(N)$+极快的查询时间$O(1)$ #### 使用场景 1. **单点更新与查询** 树状数组支持在指定位置 更新元素,并且很快地查询更新后的结果。 - 例子:使用树状数组来计算数组$nums = [0, 0, 0, 0, 0]$中某个位置$i$之前的所有元素和。首先执行单点更新操作$update(2, 5)$,将位置$2$的元素更新为$5$。然后执行查询操作$query(4)$,即查询位置$4$之前的元素和,结果为 $0 + 0 + 5 + 0 = 5$。 2. **区间更新与查询** 树状数组借助 **差分思想** 还可以 **支持对指定区间内的元素进行更新**,并且快速查询更新后的结果。 - 例子:使用树状数组来计算数组$nums = [0, 0, 0, 0, 0]$中某个区间$[l, r]$内的所有元素和。首先执行区间更新操作$rangeUpdate(2, 4, 2)$,即将位置$2$到位置$4$的元素都加$2$,对应的差分数组:$[0,2,0,0,-2]$,则从$2$到$4$都加上了$2$,然后在第$5$个位置将这个$2$扣除掉,防止对后续带来影响。 - **注:理解的不是特别通透,需要完成复习后再回顾一下。** 3. **数组逆序对统计** 树状数组可以高效地计算数组中的逆序对个数。 - 例子:给定数组$nums = [5, 2, 6, 1, 3, 4]$,使用树状数组来统计逆序对的数量。逆序对是指数组中的一对元素$(i, j)$,其中 $i < j$ 且 $nums[i] > nums[j]$。经过计算得到逆序对的数量为 $4$。 - **注**:求逆序对,一般采用归并排序或者树状数组, 一般不使用线段树,常数太大。 4. **维护最大、最小值** 树状数组维护最大值的思路与维护前缀和类似。我们可以建立一个树状数组,其中每个节点存储它所代表的区间的最大值。在 **更新** 元素时,我们需要 **沿着树状数组的路径向上更新** 所有受影响的节点,以保证它们存储的最大值始终正确。在 **查询** 区间最大值时,我们可以将查询区间分解为若干个小区间,然后 **查询这些小区间** 在树状数组中对应节点的 **最大值**,最后 **取所有查询结果的最大值** 作为最终结果。 ### 二、前置知识 **$lowbit()$运算:非负整数$x$在二进制表示下最低位$1$及其后面的$0$构成的数值** 举个栗子: $lowbit(12)=lowbit([1100]_2)=[100]_2=4$ $Code$ ```cpp {.line-numbers} int lowbit(int x){ return x & -x; } ``` ### 三、树状数组结构 树状数组的本质思想是使用 **树结构** 维护 **前缀和** ,从而把时间复杂度降为$O(log_2n)$。  ① 每个节点`t[x]`保存以`x`为根的 子树中叶节点值的和 ② **每个节点覆盖的长度 为`lowbit(x)`** ③ **`t[x]`节点的父节点为`t[x + lowbit(x)]`** ④ **树的深度为$log_2n+1$** ### 四、树状数组操作 * `add(x, k)`表示将序列中第`x`个数加上`k`。  以`add(3, 5)`为例: 在整棵树上维护这个值,需要一层一层向上找到父节点,并将这些节点上的`c[x]`值都加上`v`,这样保证计算区间和时的结果正确。时间复杂度为$O(log_2n)$。 ```cpp {.line-numbers} void add(int x, int v){ for(int i = x; i < N; i += lowbit(i)) c[i] += v; } ``` * `sum(x)`表示将查询序列前`x`个数的和  以`sum(7)`为例: 查询这个点的前缀和,需要从这个点向左上找到上一个节点,将加上其节点的值。向左上找到上一个节点,只需要将下标 `x -= lowbit(x)`,例如 `7 - lowbit(7) = 6`,`6-lowbit(6)=4`,`4-lowbit(4)=0`。 也就是$sum(7)$只需要累加上图中黄色方框部分$t[]$保存的区间和即可。 ```cpp {.line-numbers} int sum(int x){ int sum = 0; for(int i = x; i; i -= lowbit(i)) sum += c[i]; return sum; } ``` ### 五、题单 **[洛谷 $P3374$ 【模板】树状数组 1](https://www.cnblogs.com/littlehb/p/17629042.html)** **【基础模板,两个基础概念:位置+值】** **[$HDU1166$ 敌兵布阵](https://www.cnblogs.com/littlehb/p/16221667.html)** **【基础模板,两个基础概念:位置+值】** **[$AcWing$ $788$. 逆序对的数量](https://www.cnblogs.com/littlehb/p/17143537.html)** **【逆序对=树状数组+由小到大排序+去重离散化+二分+小的先进树状数组就找在当前元素右侧数字个数】** **[$POJ$ $3067$ $Japan$](https://www.cnblogs.com/littlehb/p/16228533.html)** **【二维逆序对,结构体按一维由小到大,二维由小到大排序,不使用离散化】** **[$P1966$ [$NOIP2013$ 提高组] 火柴排队](https://www.cnblogs.com/littlehb/p/17633896.html)** **【逆序对,离散化,位置+高度,按高度由小到大排序,由小到大,逐个进入树状数组,找出所有先进入但位置在我右侧的元素数量】** **[$POJ$ $2299$ $Ultra-QuickSort$](https://www.cnblogs.com/littlehb/p/16228523.html)** **【原地静态数组离散化,由小到大排序,配合$lower\_bound$,比我小,并且序号在我后面的统计个数】** **[$P2345$ [$USACO04OPEN$] 奶牛集会](https://www.cnblogs.com/littlehb/p/17629013.html)** **【两个树状数组,一个用于维护奶牛的坐标和,一个用于维护奶牛前后的个数,数学分析式子】** **[$P3368$ 【模板】树状数组 $2$ ](https://www.cnblogs.com/littlehb/p/16224866.html)** **【区间修改,单点查询,树状数组维护差分,求和就是变化值,$sum(k)+a[k]$】** **[$AcWing$ $242$. 一个简单的整数问题](https://www.cnblogs.com/littlehb/p/16141314.html)** **【区间修改,单点查询,树状数组维护差分,求和就是变化值,$sum(k)+a[k]$】** **[$LOJ$ $10117$「一本通 $4.1$ 练习 $2$」简单题](https://www.cnblogs.com/littlehb/p/17629068.html)** **【区间修改,单点查询,树状数组维护差分,求和就是变化值,$sum(k)+a[k]$】** **[$LOJ$ $10115$. 「一本通 $4.1$ 例 $3$」校门外的树](https://www.cnblogs.com/littlehb/p/17630351.html)** **【左右括号问题,两个树状数组,分别记录左括号个数,右括号个数】** **[$AcWing$ $241$ 楼兰图腾](https://www.cnblogs.com/littlehb/p/16139711.html)** **【树状数组+及时统计并用数组记录+动态单点修改】** **[$POJ$ $2352$ $Stars$ ](https://www.cnblogs.com/littlehb/p/16221116.html)** **【扫描线+树状数组】** **[$AcWing$ $243$. 一个简单的整数问题2](https://www.cnblogs.com/littlehb/p/16143694.html)** **【区间修改、区间查询(利用差分+推公式)】** > 与上面是同一道题 >**[$P3372$ 【模板】线段树$1$](https://www.cnblogs.com/littlehb/p/16224904.html)** >**[$POJ$ $3468$ $A$ $Simple$ $Problem$ $with$ $Integers$](https://www.cnblogs.com/littlehb/p/16225830.html)** >**注**:其实【区间修改,区间查询】还得是线段树,用树状数组+推公式的办法也可以做,但不是正解 **[$HDU$ $1754$ $I$ $Hate$ $It$](https://www.cnblogs.com/littlehb/p/16224335.html)** **【树状数组求最大最小值模板题】** **[$POJ$ $3264$ $Balanced$ $Lineup$](https://www.cnblogs.com/littlehb/p/16224395.html)** **【树状数组求最大最小值,在上面的题目上同时加上求最大和求最小】** **[$AcWing$ $244$. 谜一样的牛](https://www.cnblogs.com/littlehb/p/16144865.html)** **【逆向思考+树状数组维护前缀和+二分快速查找$sum=h[i]$】** **[$HDU$ $2852$ $KiKi's$ $K-Number$](https://www.cnblogs.com/littlehb/p/16225915.html)** **【逆向思考+树状数组维护前缀和+二分快速查找】** --- # 二维树状数组 ### 一、二维树状数组 二维树状数组,其实就是一维的树状数组上的节点再套个树状数组,变成了二维树状数组。 ```cpp {.line-numbers} const int N = 1e3 + 10; int c[N][N], n, m; #define lowbit(x) (x & -x) void add(int x, int y, int v) { for (int i = x; i <= n; i += lowbit(i)) for (int j = y; j <= m; j += lowbit(j)) c[i][j] += v; } LL query(int x, int y) { LL res = 0; for (int i = x; i; i -= lowbit(i)) for (int j = y; j; j -= lowbit(j)) res += c[i][j]; return res; } ``` ### 二、单点修改,区间查询 **[$LOJ$ #$133$. 二维树状数组 1:单点修改,区间查询](https://loj.ac/p/133)** > 给出一个 $n × m$ 的零矩阵 $A$ ,你需要完成如下操作: * $1$ $x$ $y$ $k$ :表示元素 $A_{x , y}$ 增加 $k$ * $2$ $a$ $b$ $c$ $d$: 表示询问左上角为 $(a,b)$ ,右下角为 $(c,d)$ 的子矩阵内所有数的和

