树状数组(Binary Indexed Tree,BIT) 是能够完成下述操作的数据结构。

  给一个初始值全为 0 的数列 a1, a2, ..., an

  (1)给定 i,计算 a1+a2+...+ai

  (2)给定 i 和 x,执行 ai += x

1.基于线段树的实现

  如果使用线段树,只需要做少许修改就可以实现这两个功能。线段树的每个节点上维护的是对应区间的和。

  

  接下来看如何计算从 s 到 t 的和(as + as+1 + ... + at)。在基于线段树的实现这个和是可以直接求得的。

  但是如果我们能够计算(从 1 到 t 的和) - (从 1 到 s - 1 的和),同样可以得到 s 到 t 的和。也就是说,只要对于任意 i,我们都能计算出 1 到 i 的部分和就可以了。

  可以发现,线段树上的每个节点的右儿子的值都不需要了(在计算时如果要使用这个点的值,那么它的左边的兄弟的值也一定会用到,这个时候只需要使用它们的父亲的值就可以了)。

  

  基于上面的思路得到的数据结构就是 BIT。比起线段树,BIT实现起来更方便,速度也更快。

2. BIT的结构

  BIT 使用数组维护下图所示的部分和

  

  也就是把线段树中不需要的节点去掉之后,再把剩下的节点对应到数组中。对比每个节点对应的区间的长度和节点编号的二进制表示。以 1 结尾的 1, 3, 5, 7 的长度是1, 最后有 1 个 0 的2, 6 的长度是2,最后有两个 0 的 4 的长度是 4 .... 这样,编号的二进制表示就能够和区间非常容易地对应起来。利用这个性质,BIT 可以通过非常简单的位运算实现。

3. BIT的求和

  计算前 i 项的和需要从 i 开始,不断把当前位置 i 的值加入到结果中, 并从 i 中减去 i 的二进制最低非 0 位对应的幂,直到 i 变为 0 为止。i 的二进制的最后一个 1 可以通过 i & - i 得到。

  

4.BIT的值的更新

  使第 i 项的值增加 x 需要从 i 开始,不断把当前位置 i 的值增加 x, 并把 i 的二进制最低非 0 位对应的幂加到 i 上。

   

5. BIT的复杂度

  总共需要对O(log n)个值进行操作,所以复杂度是O(log n)。

6. BIT的实现

  提一下 i -= i & -i 可以写为 i = i &(i - 1)

// [1, n]
int bit[MAX_N + ], n; int sum(int i) {
int s = ;
while (i > ) {
s += bit[i];
i -= i & -i;
}
return s;
} void add(int i, int x) {
while (i <= n) {
bit[i] += x;
i += i & -i;
}
}

  //本来想把代码补完整的。。参考线段树就行,但是后面会有综述我就不补了

7.  二维BIT

  BIT 可以方便地扩展到二维的情况。对于 W * H 的二维 BIT 只需建立 H 个大小为 x 轴方向元素个数 W 的 BIT,然后把这些 BIT 通过 y 轴方向的 BIT 管理起来就可以了。也就是说,y 轴方向的 BIT 的每个元素不是整数,而是一个 x 轴方向的 BIT。这样所有的操作的复杂度都是 O(log W * log H)。用同样的方法可扩展到更高的维度。

  // emm

8. 需要运用 BIT 的问题

  

  

  冒泡排序的复杂度是 O(n2),所以无法模拟过程。选用适当的数据结构可以解决这个问题。

  所求的交换次数等价于 满足 i < j , ai > aj  的 i  的个数,(这种数对的个数叫做逆序数)。而对于每一个 j,如果能够快速求出满足 i < j, ai > aj  的 i  的个数,那么问题就解决了。我们构建一个值的范围是 1 ~ n 的 BIT,按照 j = 0, 1, 2, ..., n-1 的顺序进行如下操作。

  (1)把 j - (BIT查询得到的前 aj 项的和)加到答案中

  (2)把 BIT 中 aj 位置上的值加 1

  对于每一个 j, (BIT查询得到的前 aj 项的和)就是满足i < j , ai > aj  的 i  的个数。因此把这个值从 j 中减去之后,得到的就是满足 i < j , ai > aj  的 i  的个数。由于对于每一个 j 的复杂度是O(log n),所以整个算法的复杂度是O(n log n)。

  // 但实际上实现计算逆序数更简单的算法是通过分治思想利用归并排序计算

typedef long long ll;
int n, a[MAX_N];
//省略BIT部分代码
void solve() {
ll ans = ;
for (int j = ; j < n; j++) {
ans += j - sum(a[j]);
add(a[j], );
}
printf("%lld\n", ans);
}

  

  树状数组可以高效地求出连续的一段元素之和或者更新单个元素的值。但是无法高效地给某一个区间里的所有元素同时加上一个值。因此,本题无法直接使用树状数组。在这里先考虑利用线段树来求解,然后再考虑改造树状数组来求解。

  对于每个节点,维护有该节点对应的区间的和,那么就可以在 O(log n)时间内求出任意区间的和。但这样就没有办法高效地实现对一个区间同时加一个值,因为需要对这个区间相关的所有节点都进行更新才可以。

  为了保持线段树的高效,对于每个节点我们维护以下两个数据

  (1)给这个节点对应的区间内的所有元素共同加上的值

  (2)在这个节点对应的区间中除去(1)之外其他的值的和

  通过单独维护共同加上的值,给区间同时加上一个值的操作就可以高效地进行了。如果对于父亲节点同时加了一个值,那么这个值就不会在儿子节点被重复考虑。在递归计算和时再把这一部分的值加到结果里面就可以了。这样,不论是同时加一个值还是查询一段的和的复杂度都是O(log n)。

  (1)T 是操作的种类。第 i 个操作的 T[ i ] 是 C 的话,就是给区间同时加上一个值,是 Q 的话则是查询一段的和。

  (2)A, L, R 都是以 0 为下标起点的。

  

typedef long long ll;
const int DAT_SIZE = ( << ) - ; int N, Q;
int A[MAX_N];
char T[MAX_Q];
int L[MAX_Q], R[MAX_Q], X[MAX_Q]; // 线段树
ll data[DATA_SIZE], datb[MAX_SIZE]; // 对区间[a, b]同时加 x
// k 是节点的编号,对应的区间是(l, r)
void add(int a, int b, int x, int k, int l, int r) {
if (a <= l && r <= b)
data[k] += x;
else if (l < b && a < r) {
datb[k] += (min(b, r) - max(a, l)) * x;
add(a, b, x, k * + , l, (l + r) / );
add(a, b, x, k * + , (l + r) / , r);
}
} // 对区间[a, b]同时加 x
// k 是节点的编号,对应的区间是(l, r)
ll sum(int a, int b, int k, int l, int r) {
if (b <= || r <= a) return ;
else if (a <= && r <= b) return data[k] * (r - ) + data[k];
else {
ll res = (min(b, r) - max(a, l)) * data[k];
res += sum(a, b, k * + , l, (l + r) / );
res += sum(a, b, k * + , (l + r) / , r);
return res;
}
} void solve() {
for (int i = ; i < N; i++)
add(i, i + , A[i], , , N);
for (int i = ; i < Q; i++) {
if (T[i] == 'C')
add(L[i], R[i] + , X[i], , , N);
else
printf("%lld\n", sum(L[i], R[i] + , , , N));
}
}

未完待续

树状数组(Binary Indexed Tree)的更多相关文章

  1. 树状数组(Binary Indexed Tree) 总结

    1.“树状数组”数据结构的一种应用 对含有n个元素的数组(a[1],...,a[k],...,a[n]): (1)求出第i个到第j个元素的和,sum=a[i]+...+a[j]. 进行j-i+1次加法 ...

  2. 树状数组 Binary Indexed Tree/Fenwick Tree

    2018-03-25 17:29:29 树状数组是一个比较小众的数据结构,主要应用领域是快速的对mutable array进行区间求和. 对于一般的一维情况下的区间和问题,一般有以下两种解法: 1)D ...

  3. 树状数组(Binary Indexed Tree(BIT))

    先不说别的,这个博客为我学习树状数组提供了很大帮助,奉上传送门 http://blog.csdn.net/int64ago/article/details/7429868 然后就说几个常用的操作 in ...

  4. 树状数组(Binary Index Tree)

    一维BIT(单点更新,区间求和): Problem - 1166 #include <iostream> #include <algorithm> #include <c ...

  5. 树状数组,Fenwick Tree

    Fenwick Tree, (also known as Binary Indexed Tree,二叉索引树), is a high-performance data structure to cal ...

  6. 树形DP+DFS序+树状数组 HDOJ 5293 Tree chain problem(树链问题)

    题目链接 题意: 有n个点的一棵树.其中树上有m条已知的链,每条链有一个权值.从中选出任意个不相交的链使得链的权值和最大. 思路: 树形DP.设dp[i]表示i的子树下的最优权值和,sum[i]表示不 ...

  7. HDU 3436--Queue-jumpers (树状数组 or Splay Tree)

    树状数组这个真心想了好久,还是没想出来 %%% www.cppblog.com/Yuan/archive/2010/08/18/123871.html 树状数组求前缀和大于等于k的最大值,第一次看到这 ...

  8. 树状数组(fenwick tree)

    树状数组又称芬威克树,概念上是树状,实际上是使用数组实现的,表现为一种隐式数据结构,balabala...详情请见:https://en.wikipedia.org/wiki/Fenwick_tree ...

  9. NYOJ 108 士兵杀敌1(树状数组)

    首先,要先讲讲树状数组: 树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有元素之 ...

  10. 树状数组-HDU1541-Stars一维树状数组 POJ1195-Mobile phones-二维树状数组

    树状数组,学长很早之前讲过,最近才重视起来,enmmmm... 树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据 ...

随机推荐

  1. AKKA学习(一)

    AKKA简介 什么是AKKA Akka是一个由Scala编写的,能兼容Sacala和JAVA的,用于编写高可用和高伸缩性的Actor模型框架.它基于了事件驱动的并发处理模式,性能非常的高,并且有很高的 ...

  2. 修改python pip3镜像源

    方法一: pip3 install 包名  -i 镜像源url 主要的镜像源: pip3 install tornado -i https://pypi.douban.com/simple/  pip ...

  3. Excel透视表基础之字段布局与重命名、更新、数字格式设置、空值与错误值、

    字段布局与重命名 经典布局切换 字段布局 默认布局:文本类型在行区域.数字类型在值区域. 最好用鼠标拖拽. 字段重命名 可以在字段设置中更改. 透视表更新 延迟更新 手动刷新 自动刷新 刷新注意事项 ...

  4. Linux环境下Oracle安装参数设置

    前面讲了虚拟机的设置和OracleLinux的安装,接下来我们来说下Oracle安装前的准备工作.1.系统信息查看系统信息查看首先服务器ip:192.168.8.120服务器系统:Oracle Lin ...

  5. 面试官:new一个对象有哪两个过程?

    Java在new一个对象的时候,会先查看对象所属的类有没有被加载到内存,如果没有的话,就会先通过类的全限定名来加载.加载并初始化类完成后,再进行对象的创建工作. 我们先假设是第一次使用该类,这样的话n ...

  6. WebService概念解释

    一句话概括什么是WebService   WebService是一种跨编程语言.跨操作系统平台的远程调用技术. 远程调用技术:远程调用是指一台设备上的程序A可以调用另一台设备上的方法B.比如:银联提供 ...

  7. Codeforces Round #590 (Div. 3)(e、f待补

    https://codeforces.com/contest/1234/problem/A A. Equalize Prices Again #include<bits/stdc++.h> ...

  8. 安装VUE教程

    这段时间公司要准备开始用VUE,安装的过程中就遇到各种奇葩问题 1.Node.js安装 https://nodejs.org/en/download/ 安装好noedeJS然后继续安装下一步 3.执行 ...

  9. js实现回车键搜索

    前端关键代码: <input type="text" onkeydown="entersearch()" class="form-control ...

  10. 使用elasticdump导入导出数据

    一.安装elasticdump 终端中输入 npm install elasticdump -g -g表示全局可用,直接在终端输入 elasticdump --version,出现版本信息即表示安装成 ...