参考:树状数组 - OI Wiki
树状数组是一种支持 单点修改 和 区间查询 的,代码量小的数据结构。
树状数组能解决的问题是线段树能解决的问题的子集:树状数组能做的,线段树一定能做;线段树能做的,树状数组不一定可以。
lowbit
记 x x x 二进制最低位 1
以及后面的 0
组成的数为 l o w b i t ( x ) lowbit(x) l o w bi t ( x ) 。
l o w b i t ( x ) lowbit(x) l o w bi t ( x ) 指的不是最低位 1
所在的位数 k k k ,而是这个 1
和后面所有 0
组成的 2 k 2^k 2 k
计算公式
lowbit(x) = x & -x
树状数组t[i]
的管辖区间
树状数组中,t[i]
管辖的区间长度为 2 k 2^k 2 k ,k k k 为l o w b i t ( i ) lowbit(i) l o w bi t ( i ) .
所以t[i]
的管辖区间为[ i − l o w b i t ( i ) + 1 , i ] [i-lowbit(i)+1,i] [ i − l o w bi t ( i ) + 1 , i ] 。
区间查询
以维护区间和为例,查询区间[ l , r ] [l,r] [ l , r ] 的区间和,可以化为区间[ 1 , r ] [1,r] [ 1 , r ] 和减去区间[ 1 , l − 1 ] [1,l-1] [ 1 , l − 1 ] 之和。
类似的,任意区间[ l , r ] [l,r] [ l , r ] 的信息,都可转化成查询[ 1 , l − 1 ] , [ 1 , r ] [1,l-1],[1,r] [ 1 , l − 1 ] , [ 1 , r ] 两个区间的信息再做相应操作得到。
查询区间[ 1 , x ] [1,x] [ 1 , x ] 的过程:
从 t [ x ] t[x] t [ x ] 开始往前跳,有 t [ x ] t[x] t [ x ] 管辖 [ x − l o w b i t ( x ) + 1 , x ] [x-lowbit(x)+1,x] [ x − l o w bi t ( x ) + 1 , x ] ;
令 x ← x − l o w b i t ( x ) x \leftarrow x-lowbit(x) x ← x − l o w bi t ( x ) ,如果 x = 0 x=0 x = 0 说明已经跳到尽头了,终止循环;否则回到第一步。
将跳到的 t [ x ] t[x] t [ x ] 合并。
1 2 3 4 5 6 7 8 9 int get (int x) { int ans=0 ; while (x>0 ) { ans+=t[x]; x = x-lowbit (x); } return ans; }
单点修改
我们的目标是快速正确地维护 t t t 数组。为保证效率,我们只需遍历并修改管辖了 a [ x ] a[x] a [ x ] 的所有 t [ j ] t[j] t [ j ] ,因为其他的 t t t 显然没有发生变化。
管辖a [ x ] a[x] a [ x ] 的 t [ j ] t[j] t [ j ] 一定包含 t [ x ] t[x] t [ x ] (根据性质 1),所以 j j j 在树状数组树形态上是 x x x 的祖先。因此我们从 x x x 开始不断跳父亲,直到跳得超过了原数组长度为止。
设n n n 表示 a a a 的大小,不难写出单点修改 a [ x ] a[x] a [ x ] 的过程:
初始令 i ← x i \leftarrow x i ← x 。
修改 c [ i ] c[i] c [ i ] 。
令 i ← i + l o w b i t ( i ) i\leftarrow i+lowbit(i) i ← i + l o w bi t ( i ) ,如果 i > n i>n i > n 说明已经跳到尽头了,终止循环;否则回到第二步。
1 2 3 4 5 6 7 void add (int x,int k) { int i=x; while (i<=n) { c[i] += k; i = i + lowbit (i); } }
建树
一般可以直接转化为 n n n 次单点修改,时间复杂度Θ ( N log N ) \Theta(N\log N) Θ ( N log N )
例题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include <bits/stdc++.h> #define int long long #define IOS ios::sync_with_stdio(false);cin.tie(nullptr); #define endl '\n' using namespace std;const int N = 5e5 +10 ;struct BIT { int t[N]; int n; int lowbit (int x) { return x & -x; } void update (int i,int k) { int x=i; while (x<=n) { t[x]+=k; x+=lowbit (x); } } int count (int x) { int ans=0 ; while (x>0 ) { ans+=t[x]; x-=lowbit (x); } return ans; } int query (int l,int r) { return count (r)-count (l-1 ); } }; BIT bit; signed main () { IOS int n,m;cin>>n>>m; bit.n=n; for (int i=1 ;i<=n;i++) { int a;cin>>a; bit.update (i,a); } while (m--) { int op,x,y;cin>>op>>x>>y; if (op==1 ) { bit.update (x,y); } else { cout<<bit.query (x,y)<<endl; } } return 0 ; }
区间修改,单点查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <bits/stdc++.h> #define ll long long #define IOS ios::sync_with_stdio(false);cin.tie(nullptr); #define endl '\n' using namespace std;const int N = 5e5 +10 ;int n,m;ll c[N]; ll lowbit (ll x) { return x&-x; } void add (int pos,ll x) { for (int i=pos;i<=n;i+=lowbit (i)) { c[i]+=x; } } void add (int l,int r,ll x) { add (l,x),add (r+1 ,-x); } ll query (int pos) { ll ans=0 ; for (int i=pos;i>0 ;i-=lowbit (i)) { ans+=c[i]; } return ans; } signed main () { IOS cin>>n>>m; ll last=0 ; for (int i=1 ;i<=n;i++) { ll a;cin>>a; add (i,a-last); last=a; } while (m--) { int op;cin>>op; if (op == 1 ) { ll x,y,k;cin>>x>>y>>k; add (x,y,k); } else if (op==2 ) { ll x;cin>>x; cout<<query (x)<<endl; } } return 0 ; }
区间修改区间查询
根据上述的前缀和单点查询操作,可以得到原数组的前缀和求法如下
∑ i = 1 p a [ i ] = ∑ i = 1 p ∑ j = i i d [ j ] \sum_{i=1}^{p} a[i] = \sum_{i=1}^{p} \sum_{j=i}^{i} d[j]
i = 1 ∑ p a [ i ] = i = 1 ∑ p j = i ∑ i d [ j ]
= p × d [ 1 ] + ( p − 1 ) × d [ 2 ] + . . . + d [ p ] = ∑ i = 1 p d [ i ] × ( p − i + 1 ) =p\times d[1]+(p-1)\times d[2] +...+d[p] = \sum_{i=1}^{p} d[i]\times(p-i+1)
= p × d [ 1 ] + ( p − 1 ) × d [ 2 ] + ... + d [ p ] = i = 1 ∑ p d [ i ] × ( p − i + 1 )
= ( p + 1 ) ∑ i = 1 p d [ i ] − ∑ i = 1 p d [ i ] × i =(p+1)\sum_{i=1}^{p} d[i] - \sum_{i=1}^{p} d[i] \times i
= ( p + 1 ) i = 1 ∑ p d [ i ] − i = 1 ∑ p d [ i ] × i
因此可以维护两个数组的前缀和A 1 [ i ] = d [ i ] A_1[i] = d[i] A 1 [ i ] = d [ i ] ,A 2 [ i ] = d [ i ] × i A_2[i] = d[i]\times i A 2 [ i ] = d [ i ] × i
易得A 1 [ i ] = d [ i ] + A 1 [ i − 1 ] A_1[i] = d[i]+A_1[i-1] A 1 [ i ] = d [ i ] + A 1 [ i − 1 ] ,A 2 [ i ] = d [ i ] × i + A 2 [ i − 1 ] A_2[i] = d[i]\times i +A_2[i-1] A 2 [ i ] = d [ i ] × i + A 2 [ i − 1 ]
单点查询操作不变
区间查询[1,p]
为
1 2 3 4 5 6 7 int query (int p) { int ans=0 ; for (int i=p;i>0 ;i-=lowbit (i)) { ans += (p+1 ) * A1[i] - A2[i]; } return ans; }
1 2 3 int query (int l,int r) { return query (r) - query (l-1 ); }
1 2 3 4 5 6 void add (int p,int x) { for (int i=p;i<=n;i+=lowbit (i)) { A1[i] += x; A2[i] += x*p; } }
1 2 3 void add (int l,int r,int x) { add (l,x),add (r+1 ,-x); }