树状数组详解及其应用
前言:
之前以为树状数组搞懂了,结果一个树状数组题把我打回原形,原来我只是会背模板而已。。前来复习 学习一遍
为什么用树状数组
树状数组是一种存储数据的结构,通过压缩数据来达到高效的查询效率
 在一些题目中需要多次查询区间和,同时更改点,如果我们暴力去更改和查询复杂度是O(n),如果用前缀和实现,虽然查询快但是更改是On,但是用树状数组维护,查询和更改都是O(logn),当然不只是单点修改+区间询问,在区间修改+区间询问,单点修改+区间最大值,逆序对查询都有很好的表现
怎么实现树状数组
树状数组怎么形成(有什么规律)
首先,树状数组和树形结构有什么关系呢
这是一颗树
 
 这是树状数组
 
 可以发现树状数组其实就是树形结构整体向右拉,树状数组中每个点包含着一个或者多个值相加的结果
 那么我们把树状数组和原数组的对应关系再写详细一些,看看其中的规律
 C1 = A1
 C2 = C1 + A2 = A1 + A2
 C3 = A3
 C4 = C2 + C3 + A4 = A1 + A2 + A3 + A4
 C5 = A5
 C6 = C5 + A6 = A5 + A6
 C7 = A7
 C8 = C4 + C6 + C7 + A8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
 可能现在看不出来规律,那我们把它写成二进制看看
 
 观察树状数组中最后一位1的位置,可以发现,每个树状数组包含的原数组的个数等于树状数组二进制最后一位1和后面的数组成的数
 比如6(110)最后一位1形成的数为2(10),所以C6 包含两项
 因此总结出公式
 C[i] = A[i - 2^k+1] + A[i - 2^k+2] + … + A[i];
lowbit函数(形成树状数组的关键)
刚刚所说的二进制最后一位1和后面的数组成的数,这么抽象,到底要怎么写呢
 其实很简单,有两种写法
int lowbit1(int x) return x&(x^(x-1);
int lowbit2(int x) return x&-x;
 
这里两种写法都可以,首选第二种,因为更短,第二种巧妙利用了负数是补码的特性,
 那么有了lowbi函数我们就可以轻松知道树状数组中下标为 i 包含原数组就是
 [ i - lowbit(i), i ]
实际应用
单点修改 + 区间和询问
区间询问
这是树状数组中最为经典的用法
 我们已经知道树状数组中下标为 i 包含原数组就是[ i - lowbit(i), i ],那么我们想要求出原数组[1,i]前缀和,那每次我们都计算[ i - lowbit(i), i ],然后让i减去它所包含的项目个数,也就是 i -= lowbit(i),直到出现i等于0,就可能把1到i的前缀和算出来
int getsum(int ix){      //求A[1 - ix]的和
    int res = 0;
    for(int i = ix;i>0;i-=lowbit(i)){
    	res += tree[i];
    }
    return res;
}
 
单点修改
由于在树状数组,每个值包含的不是原数组了,当修改单点时会影响后面多个树状数组值,当你修改了i的值时,后面包含该值的所有数都会变化
 因为 A[i] 包含于 C[i + 2^k]、C[(i + 2^k) + 2^k],也就是你每次修改的下标都是i+lowbit(i)
void update(int ix,int x){
	for(int i = ix;i<=n;i+=lowbit(i){
		tree[i] += x;
	}
}
 
所以单点修改 + 区间和询问的代码为
int A[100005],tree[400005]
int lowbit(int x) return x&-x;
void update(int ix,int x){  //在ix位置加上x值
	for(int i = ix;i<=n;i+=lowbit(i){
		tree[i] += x;
	}
}
int getsum(int ix){      //求A[1 - ix]的和
    int res = 0;
    for(int i = ix;i>0;i-=lowbit(i)){
    	res += tree[i];
    }
    return res;
}
 
区间修改 + 区间询问
可能你会问 区间修改 + 单点询问 呢?用两次区间询问相减就是单点询问了
 但是如果我们只有一次单点询问,我们有更常用的方法那就是差分数组,有了这个启发,很容易去想用树状数组去实现差分数组
 所以解决区间修改 + 区间询问就是差分树状数组
区间修改
对于普通的差分数组修改来说 [l,r]增加x 只需要 A[l] += x , A[r] += x,即可,那对于树状数组来说其实是一样的,树状数组就是数据结构,本质上还是个数组
void add(ll t[],ll ix,ll x){
    for(int i = ix;i<=n;i+=(i&-i)){
        t[i] += x;
    }
} 
cin>>c; 
add(tree, b + 1,-c);
add(tr,b+1,(b + 1)*-c);
 
区间询问
压力就给到了区间询问这边
 在差分数组中我们查询单点时,需要计算前缀和,那区间询问我们就要计算多次前缀和吗? 显然不用,我们来看看规律
 A1 = C1
 A2 = C1 + C2
 A3 = C1 + C2 + C3
 A4 = C1 + C2 + C3 + C4
因此我们有 A n = ∑ i = 1 n C i A_n = \sum_{i=1}^n C_i An=∑i=1nCi
设Sn 是 区间和 ,那么
 S1 = A1
 S2 = A1 + A2
 S3 = A1 + A2 + A3
 S4 = A1 + A2 + A3 + A4
 因此我们有 
    
     
      
       
        
         S
        
        
         n
        
       
       
        =
       
       
        
         ∑
        
        
         
          i
         
         
          =
         
         
          1
         
        
        
         n
        
       
       
        
         A
        
        
         i
        
       
      
      
       S_n = \sum_{i=1}^n A_i
      
     
    Sn=∑i=1nAi
 结合起来
 S1 = C1
 S2 = 2C1 + C2
 S3 = 3C1 + 2C2 + C3
 S4 = 4C1 + 3C2 + 2C3 + C4
 因此我们有
     
      
       
        
         
          S
         
         
          n
         
        
        
         =
        
        
         
          ∑
         
         
          
           i
          
          
           =
          
          
           1
          
         
         
          n
         
        
        
         
          ∑
         
         
          
           j
          
          
           =
          
          
           1
          
         
         
          i
         
        
        
         
          A
         
         
          j
         
        
        
         =
        
        
         
          ∑
         
         
          
           i
          
          
           =
          
          
           1
          
         
         
          n
         
        
        
         (
        
        
         n
        
        
         −
        
        
         i
        
        
         +
        
        
         1
        
        
         )
        
        
         
          C
         
         
          i
         
        
        
         =
        
        
         
          ∑
         
         
          
           i
          
          
           =
          
          
           1
          
         
         
          n
         
        
        
         (
        
        
         n
        
        
         +
        
        
         1
        
        
         )
        
        
         C
        
        
         i
        
        
         −
        
        
         
          ∑
         
         
          
           i
          
          
           =
          
          
           1
          
         
         
          n
         
        
        
         i
        
        
         
          C
         
         
          i
         
        
       
       
        S_n = \sum_{i=1}^n \sum_{j=1}^i A_j =\sum_{i=1}^n(n-i+1)C_i=\sum_{i=1}^n(n+1)Ci-\sum_{i=1}^niC_i
       
      
     Sn=i=1∑nj=1∑iAj=i=1∑n(n−i+1)Ci=i=1∑n(n+1)Ci−i=1∑niCi
 所以我们计算区间和要用两个树状数组,一个记录 i*Ci的前缀和一个记录Ci的前缀和
ACwing243. 一个简单的整数问题2
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+7;
typedef long long ll;
ll tree[N],tr[N],n;
void add(ll t[],ll ix,ll x){
    for(int i = ix;i<=n;i+=(i&-i)){
        t[i] += x;
    }
} 
ll ask(ll t[],ll ix){
    ll res = 0;
    for(int i = ix;i>0;i-=(i&-i)){
        res += t[i];
    }
    return res;
}
ll A[N],m;
ll sum(ll ix){
    return (ix+1)*ask(tree,ix)-ask(tr,ix);
}
int main(){
    cin>>n>>m;
    int pre = 0;
    for(int i = 1;i<=n;i++){
        cin>>A[i];
        add(tree,i,A[i]-A[i-1]);
        add(tr,i,i*(A[i]-A[i-1]));
    }
    while(m--){
        char ch;
        int a,b,c;
        cin>>ch>>a>>b;
        if(ch=='Q'){
            cout<<sum(b)-sum(a-1)<<endl;
        }
        else {
            cin>>c;
            add(tree,a,c), add(tree, b + 1,-c);
            add(tr,a,a*c), add(tr,b+1,(b + 1)*-c);
        }
    }
    return 0;
}
 
有待更新










