树状数组应用汇总-二维区间查询,区间修改差分公式推导

树状数组个人总结


单点修改,区间查询

int tree[maxn];
inline int lowbit(int x){
    return x&(-x);
}
inline void update(int x,int val){ //将x处的值增加val
    for(int i=x;i<maxn;i+=lowbit(i)){
        tree[i]+=val;
    }
}
inline int query(int x){ //求[1,x]的所有值的和
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i)){
        ans+=tree[i];
    }
    return ans;
}

单点修改,区间最值

注意修改某个位置x的值的时候,也要修改a[x]的值

int tree[maxn],a[maxn];
inline int lowbit(int x){
    return x&(-x);
}
inline void update(int x,int val,int n){
    for(int i=x;i<=n;i+=lowbit(i)){
        tree[i]=val;
        for(int j=1;j<lowbit(i);j<<=1){
            tree[i]=max(tree[i],tree[i-j]);
        }
    }
}
inline int query(int x,int y){
    int ans=0;
    while(x<=y){
        ans=max(ans,a[y]);
        for(y-=1;y-lowbit(y)>=x;y-=lowbit(y)){
            ans=max(ans,tree[y]);
        }
    }
    return ans;
}

求第k小的值

思路:

​	若对于a数组,a[i]=1表示,数列中存在i这个值,那么如果用最直接的方式求第k小

就是从小到大遍历计数,直到找到k个数。这种方法是O(n)的时间复杂度

​	利用树状数组求就是二进制的思想,每次不是一个格子一个格子的求,而是多个求。

例如数列:236810,求第k=4小的数

  对于原始数组a 1 2 3 4 5 6 7 8 9 10

​	  数组内值 0 1 1 0 0 1 0 1 0 1

ans为当前得到的数的值,cnt为上次得到的数所在的排名,即第cnt小的数。

t=log2(10)=3;

t=3时,ans=0+2^3=8<10,cnt+tree[8]=a[1]+a[2]+……+a[8]=4==4,所以ans-=8=0;

t=2时,ans=0+2^2=4<10,cnt+tree[4]=0+a[1]+a[2]+a[3]+a[4]=2<k,所以cnt+=2=2;

t=1时,ans=4+2^1=6<10,cnt+tree[6]=2+a[5]+a[6]=3<k,所以cnt+=1=3;

t=0时,ans=6+1=7<10,cnt+tree[7]=3+a[7]=3<k,所以cnt+=0=3;

所以第4小的值为ans+1=8.
int tree[maxn];
inline int lowbit(int x){
    return x&(-x);
}
inline void update(int x,int val){ //将x处的值增加val,此处val一般为1
    for(int i=x;i<maxn;i+=lowbit(i)){
        tree[i]+=val;
    }
}
inline int query(int x){ //求[1,x]的所有值的和
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i)){
        ans==tree[i];
    }
    return ans;
}
inline get_kth(int k,int n){ //求第k小的数的值,n为最大的数的值,一般比最大数的值大1
    int ans=0,cnt=0; //ans为当前是哪个数,cnt为上个数排名为多少
    for(int i=log2(n);i>=0;i--){
        ans+=1<<i;
        if(ans>=n||cnt+tree[ans]>=k) ans-=1<<i; //若当前数大于最大的数,或者排名大于k,回退
        else cnt+=tree[ans];
    }
    return ans+1;
}

区间修改,单点查询

思路:

​ 利用差分的思想,即:
a k = ∑ i = 1 k d i , 其 中 d i = a i − a i − 1 a_k=\sum_{i=1}^kd_i,其中d_i=a_i-a_{i-1} ak=i=1kdidi=aiai1
由上式可以看出某个位置的值只和起点的值大小有关,所以修改区间的时候,只需要修改端点的值即可。

int tree[maxn];
inline int lowbit(int x){
    return x&(-x);
}
inline void update(int x,int val){
    for(int i=x;i<maxn;i+=lowbit(i)){
        tree[i]+=val;
    }
}
inline int query(int x){
	int ans=0;
    for(int i=x;i>0;i-=lowbit(i)){
        ans+=tree[i];
    }
    return ans;
}
int a[maxn];
int main(){
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",a+i),update(i,a[i]-a[i-1]); //初始化
   	update(2,10);update(8,-10); //将区间[2,7]的所有数的值增加10
   	printf("%d\n%,query(4)); //查询[4]处的值
}

区间修改,区间查询

思路:

​ 也是利用差分的思想:
对 于 a k 我 们 知 道 a k = ∑ i = 1 k d i 则 ∑ i = 1 k a i = ∑ i = 1 k ∑ j = 1 i d j = ∑ i = 1 k ( k + 1 − i ) ⋅ d i 则 可 得 ∑ i = 1 k a i = ∑ i = 1 k ( k + 1 ) ⋅ d i − ∑ i = 1 k i ⋅ d i = ( k + 1 ) ⋅ ∑ i = 1 k d i − ∑ i = 1 k i ⋅ d i 对于a_k我们知道a_k=\sum_{i=1}^kd_i\\ 则\sum_{i=1}^ka_i=\sum_{i=1}^k\sum_{j=1}^id_j=\sum_{i=1}^k(k+1-i)\cdot d_i\\ 则可得\sum_{i=1}^ka_i=\sum_{i=1}^k(k+1)\cdot d_i-\sum_{i=1}^ki\cdot d_i=(k+1)\cdot \sum_{i=1}^kd_i-\sum_{i=1}^ki\cdot d_i akak=i=1kdii=1kai=i=1kj=1idj=i=1k(k+1i)dii=1kai=i=1k(k+1)dii=1kidi=(k+1)i=1kdii=1kidi
则可通过树状数组维护两个数组分别存储 d i d_i di i ⋅ d i i\cdot d_i idi,更新区间的时候与上面一样只需要更新端点即可。

若要修改的区间为[x,y],则要更新的为update(x,val);update(y+1,-val);

若要查询的区间为[x,y],则查询的为query(y)-query(x-1);

int tree1[maxn],tree2[maxn];
inline int lowbit(int x){
    return x&(-x);
}
inline void update(int x,int val){
    for(int i=x;i<maxn;i+=lowbit(i)){
        tree1[i]+=val;tree2[i]+=x*val;
    }
}
inline int query(int x){
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i)){
        ans+=(x+1)*tree1[i]-tree2[i];
    }
    return ans;
}
int a[maxn];
int main(){
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",a+i),update(i,a[i]-a[i-1]); //初始化
    update(2,10);update(8,-10); //将区间[2,7]所有的值增加10
    printf("%d\n",query(6)-query(4-1)); //查询区间[4,6]的和
}

高维树状数组

其他不说,简单修改就可以,对于最后的区间查询,区间修改简单推导如下:
因 为 a n m = ∑ i = 1 n ∑ j = 1 m d i j , 其 中 d i j = a i j − a ( i − 1 ) j ˙ − a i ( ˙ j − 1 ) + a ( i − 1 ) ( j − 1 ) 则 ∑ i = 1 n ∑ j = 1 m a i j = ∑ i = 1 n ∑ j = 1 m ∑ k = 1 i ∑ p = 1 j d k p = ∑ i = 1 n ∑ j = 1 m ( m ⋅ n − ( i − 1 ) ⋅ m − ( j − 1 ) ⋅ n + ( i − 1 ) ⋅ ( j − 1 ) ) ⋅ d i j 则 可 得 ∑ i = 1 n ∑ j = 1 m a i j = ( m ⋅ n ) ⋅ ∑ i = 1 n ∑ j = 1 m d i j − m ⋅ ∑ i = 1 n ∑ j = 1 m ( i − 1 ) ⋅ d i j − n ⋅ ∑ i = 1 n ∑ j = 1 m ( j − 1 ) ⋅ d i j + ∑ i = 1 n ∑ j = 1 m ( i − 1 ) ⋅ ( j − 1 ) ⋅ d i j 因为a_{nm}=\sum_{i=1}^n\sum_{j=1}^md_{ij},其中d_{ij}=a_{ij}-a_{(i-1)\dot j}-a_{i\dot (j-1)}+a_{(i-1)(j-1)} \\ 则\sum_{i=1}^n\sum_{j=1}^ma_{ij}=\sum_{i=1}^n\sum_{j=1}^m\sum_{k=1}^i\sum_{p=1}^jd_{kp}=\sum_{i=1}^n\sum_{j=1}^m(m\cdot n-(i-1)\cdot m- (j-1)\cdot n+(i-1)\cdot (j-1))\cdot d_{ij} \\则可得\sum_{i=1}^n\sum_{j=1}^ma_{ij}=\\(m\cdot n)\cdot\sum_{i=1}^n\sum_{j=1}^md_{ij}-m\cdot\sum_{i=1}^n\sum_{j=1}^m(i-1)\cdot d_{ij}-n\cdot\sum_{i=1}^n\sum_{j=1}^m(j-1)\cdot d_{ij}+\sum_{i=1}^n\sum_{j=1}^m(i-1)\cdot(j-1)\cdot d_{ij} anm=i=1nj=1mdijdij=aija(i1)j˙ai(˙j1)+a(i1)(j1)i=1nj=1maij=i=1nj=1mk=1ip=1jdkp=i=1nj=1m(mn(i1)m(j1)n+(i1)(j1))diji=1nj=1maij=(mn)i=1nj=1mdijmi=1nj=1m(i1)dijni=1nj=1m(j1)dij+i=1nj=1m(i1)(j1)dij
所以需要用四个数组分别维护 d i j ​ d_{ij}​ dij ( i − 1 ) ⋅ d i j ​ (i-1)\cdot d_{ij}​ (i1)dij ( j − 1 ) ⋅ d i j ​ (j-1)\cdot d_{ij}​ (j1)dij ( i − 1 ) ⋅ ( j − 1 ) ⋅ d i j ​ (i-1)\cdot (j-1)\cdot d_{ij}​ (i1)(j1)dij

若要修改的矩形区间为[x1,y1]-[x2,y2],则更新的点为

update(x1,y1,val);update(x2+1,y2+1,val);update(x1,y2+1,-val);update(x2+1,y1,-val);

若要查询的举行区间为[x1,y1]-[x2,y2],则查询的值为

query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1)

int tree1[maxn][maxn],tree2[maxn][maxn],tree3[maxn][maxn],tree4[maxn][maxn];
inline int lowbit(int x){
    return x&(-x);
}
inline void update(int x,int y,int val){
    for(int i=x;i<maxn;i+=lowbit(i)){
        for(int j=y;j<maxn;j+=lowbit(j)){
            tree1[i][j]+=val;tree2[i][j]+=(x-1)*val;
            tree3[i][j]+=(y-1)*val;tree4[i][j]+=(x-1)*(y-1)*val;
        }
    }
}
inline int query(int x,int y){
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i)){
        for(int j=y;j>0;j-=lowbit(j)){
            ans+=(x*y)*tree1[i][j]-y*tree2[i][j]-x*tree3[i][j]+tree4[i][j];
        }
    }
    return ans;
}
int a[maxn][maxn];
int main(){
    int n,m;scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
            update(i,j,a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]);
        }
    }
    update(2,3,10);update(7,6,10);update(2,6,-10);update(7,3,-10); 
    //将矩形区间[2,3]-[6,5]所有的值增加10
    printf("%d\n",query(4,5)+query(2,1)-query(2,5)-query(4,2)); 
    //查询矩形区间[3,2]-[4,5]的和
}

你可能感兴趣的:(数据结构)