一.介绍
树状数组(Fenwick Tree),也称为二叉索引树(Binary Indexed Tree,BIT),是一种用于高效处理动态数组前缀和的数据结构。它可以在O(log n)的时间复杂度内完成单点更新和区间查询操作。
树状数组的主要应用是计算数组的前缀和。它通过将数组元素按照二进制表示的索引进行组织,使得每个节点存储一定范围内的元素的和。通过利用二进制的特性,树状数组可以高效地进行更新和查询操作。
树状数组的基本操作包括:
- 单点更新(Update):将指定位置的元素值增加或减少一个给定的增量。
- 区间查询(Query):计算指定区间内的元素和。
树状数组的构建过程如下:
- 初始化一个长度为n的数组,并将所有元素初始化为0。
- 对于每个位置i,将原始数组中的元素依次加到树状数组中,同时更新相关节点的值。
树状数组的更新操作可以通过不断将当前位置的索引加上其最低位的1来实现。查询操作可以通过不断将当前位置的索引减去其最低位的1来实现。
树状数组的优点是实现简单、空间效率高,并且支持高效的单点更新和区间查询操作。它在解决一些与前缀和相关的问题时非常有用,比如计算逆序对、求解逆序数等。
二.详细讲解
(1)建树
若我们原数组为{1,2,3,4,5,6,7,8,9};
我们不妨可以这样加快区间合计算速度。
当我们求:
c[1-9]=c[1-8]+c[9]
c[1-8]=c[1-8]
c[1-7]=c[1-4]+[5-6]+c[7]
c[1-6]=c[1-4]+c[5-6]
c[1-5]=c[1-4]+c[5]
.........
这样的话我们修改一个单值,只需要再修改它的父节点即可。
但我们再仔细看看会发现在求c[1-?]数组时都不需要用到右子树。
因为是奇数,加上即可,是偶数直接是父节点,根本不需要右子树。我们优化掉。
于是树就是这样了:
一棵树没了右子树,自然节点就从O(n)-->O(logn);
只也就算修改值只需要O(logn)的由来。
(2)建立/修改c数组
我们可以实现树了,这里就可以用代码来实现。
因为是树,所以可以使用二进制辅助。
一个父节点有多少左子节点呢?其实就是它数组下标值的位权值。
位权值就是x&(-x)求得。
位权值就是c数组包含的原数组元素的个数。
所以c[8]有a数组的8个元素,即: c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];
所以c[6]有a数组的2个元素,即: a[5]+a[6];
总结:c[x]有a数组的(x&(-x))个元素,即:c[x]= a[x-lowbit(x)+1]+....+a[x];
注意的是修改就要一次修改彻底,所以添加/修改就要修改它所以的父节点
一个数x,它的父节点就是x+=(x&(-x));
修改元素的函数如下:
void update(int x,int y){ //下标 && 值 for(;x<=n;x+=(x&(-x))) {c[x]+=y;}
}
(3)查询区间
查询区间其实就是修改区间的逆运算。
就是累加它的子节点,查询函数为:
int fun(int x){ //return [1,x]int ans=0;while(x){ans+=c[x];x-=(x&(-x));}return ans;
}
但这只能查询1-x啊,x-y怎么办?
求fun(y)-fun(x-1)不就行了嘛。
即a[3-8]=a[1-8]-a[1-2];
三.模板题
P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述:
有n个整数,可能会有两种操作: 1.将某一个数加上x;2.求出某区间数的和。
输入要求第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3个整数,表示一个操作,具体如下:操作1: 格式:1x k,将第x个数加上k格式:2xy
操作2:输出区间[x,y]内每个数的和
样例输入:
5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
样例输出:
14
16
四 .参考代码
#include<bits/stdc++.h>
#define maxn 500005
using namespace std;
int n,m;
int a[maxn]; //原数组
int c[maxn]; //树状树状
void update(int x,int y){ //下标 && 值 for(;x<=n;x+=(x&(-x))) {c[x]+=y;}
}
int fun(int x){ //return [1,x]int ans=0;while(x){ans+=c[x];x-=(x&(-x));}return ans;
}
int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){cin>>a[i];update(i,a[i]); //建立树状数组 } while(m--){int op,x,y;scanf("%d%d%d",&op,&x,&y);if(op==1){update(x,y);}else{ //[3,7]=[1,7]-[1,2]cout<<fun(y)-fun(x-1)<<endl;}}return 0;
}