zoukankan      html  css  js  c++  java
  • 树状数组

    一、引言

      1.什么是树状数组?

        顾名思义,就是用数组来模拟树形结构呗。那么衍生出一个问题,为什么不直接建树?答案是没必要,因为树状数组能处理的问题就没必要建树。和Trie树的构造方式有类似之处。

      2.树状数组可以解决什么问题

        可以解决大部分基于区间上的更新以及求和问题。

      3.树状数组和线段树的区别在哪里

        树状数组可以解决的问题都可以用线段树解决,这两者的区别在哪里呢?树状数组的系数要少很多,就比如字符串模拟大数可以解决大数问题,也可以解决1+1的问题,但没人会在1+1的问题上用大数模拟。

      4.树状数组的优点和缺点

        修改和查询的复杂度都是O(logN),而且相比线段树系数要少很多,比传统数组要快,而且容易写。

        缺点是遇到复杂的区间问题还是不能解决,功能还是有限。

    二、基本思想

        算数基本定理:可以将任意正整数关于2的不重复次幂的唯一分解性质,一个数21可以分解为,2^0+2^2+2^4,则一个区间[1,n]可以二进制分解为log(x)个区间

          例如:log(21)=3;

              ①长度为2^4  [1,2^4]

              ②长度为2^2  [2^4+1,2^4+2^2]

              ③[长度为2^0  [2^4+2^2+1,2^4+2^2+2^0]

         树状数组就是依赖算术基本定理的分解思想,把一个区间分解为log(n)个小区间,分而治之

    三、基本算法

       若区间结尾为R,则区间长度就等于R的二进制分解下最小的二次幂,这时引入lowbit(n)

       对于序列A,我们建立一个数组c,数组c保存序列A中[x-lowbit[x]+1,x]中所有数的和

       该结构满足一下性质:

      •   每个节点c[x]保存以它为根的子树中所有叶子节点的和
      •   每个内部节点c[x]的子节点个数等于lowbit(x)的大小
      •   除了树根之外,每一个结点c[x]的父亲是c[x+lowbit[x]]
      •   树的深度为log(N)

      1.求lowbit(x)

        原理:将x用二进制表示,将x取反的基础上再加上1,所以原来最后一位到倒着数原来为1的位置(不包含),这些位置原来从0变成1,又因为加上了一,所以就会进位

           原来最后一位到倒着数原来为1的位置(不包含)就会又变成0,直到原来为1的位置与原来是一样的,所以这样就将非负整数x二进制表示下最低位1与后面的0构成的值用lowbit(x)表示

           用因为在补码的表示下~x=-1-n

           所以lowbit(x)=x&(-x);

      2.对某个元素进行加减法操作

        对a[x]对于修改,c[x]以及c[x]的祖先都需要修改,又因为c[x]的祖先为c[x+lowbit(x)],所以可以在log(n)的时间内执行单点增加以及前缀和维护操作

      3.查询前缀和

        对于一个前缀和[1,n],我们把它划分为了log(n)个小区间,想基本思想中举的例子一样,对于区间和,则等于这几个小区间的区间和总值

      4.查询区间和

        利用前缀和求解,sum[i,j]=sum[j]-sum[i-1]

      5.扩展(多维树状数组)

        跟一维的差不多,知识多了几个循环,时间复杂度为(logn)^m,在维度不大的情况下,还是可以接受的

      6.注意事项

        下标不能为0,lowbit(0) ,会陷入死循环

    四、典例分析

       模板1(单点修改)

    #include <bits/stdc++.h>
    using namespace std;
    
    int n,m;
    int a[50005],c[50005]; //对应原数组和树状数组
    
    int lowbit(int x){
        return x&(-x);
    }
    
    void updata(int i,int k){    //在i位置加上k
        while(i <= n){
            c[i] += k;
            i += lowbit(i);
        }
    }
    
    int getsum(int i){        //求A[1 - i]的和
        int res = 0;
        while(i > 0){
            res += c[i];
            i -= lowbit(i);
        }
        return res;
    }
    
    int main(){
        int t;
        cin>>t;
        for(int tot = 1; tot <= t; tot++){
            cout << "Case " << tot << ":" << endl;
            memset(a, 0, sizeof a);
            memset(c, 0, sizeof c);
            cin>>n;
            for(int i = 1; i <= n; i++){
                cin>>a[i];
                updata(i,a[i]);   //输入初值的时候,也相当于更新了值
            }
    
            string s;
            int x,y;
            while(cin>>s && s[0] != 'E'){
                cin>>x>>y;
                if(s[0] == 'Q'){    //求和操作
                    int sum = getsum(y) - getsum(x-1);    //x-y区间和也就等于1-y区间和减去1-(x-1)区间和
                    cout << sum << endl;
                }
                else if(s[0] == 'A'){
                    updata(x,y);
                }
                else if(s[0] == 'S'){
                    updata(x,-y);    //减去操作,即为加上相反数
                }
            }
    
        }
        return 0;
    }

       模板2(区间修改)

    int n,m;
    int a[50005] = {0};
    int sum1[50005];    //(D[1] + D[2] + ... + D[n])
    int sum2[50005];    //(1*D[1] + 2*D[2] + ... + n*D[n])
    
    int lowbit(int x){
        return x&(-x);
    }
    
    void updata(int i,int k){
        int x = i;    //因为x不变,所以得先保存i值
        while(i <= n){
            sum1[i] += k;
            sum2[i] += k * (x-1);
            i += lowbit(i);
        }
    }
    
    int getsum(int i){        //求前缀和
        int res = 0, x = i;
        while(i > 0){
            res += x * sum1[i] - sum2[i];
            i -= lowbit(i);
        }
        return res;
    }
    
    int main(){
        cin>>n;
        for(int i = 1; i <= n; i++){
            cin>>a[i];
            updata(i,a[i] - a[i-1]);   //输入初值的时候,也相当于更新了值
        }
    
        //[x,y]区间内加上k
        updata(x,k);    //A[x] - A[x-1]增加k
        updata(y+1,-k);        //A[y+1] - A[y]减少k
    
        //求[x,y]区间和
        int sum = getsum(y) - getsum(x-1);
    
        return 0;
    }

    五、相关转载与推荐文章(十分感谢这些博主)

        树状数组详解

    l

  • 相关阅读:
    josn类库引用
    WPF圆角按钮
    C#实现某一属性值变化时触发事件 Form1_changeEvent是对应的事件
    C#winform生成安装包
    特性
    反射可以动态调用对象(一般是类)的名称,属性,方法等。具体见下。重要
    原子操作 和Inerlocked 常用于多线程同步
    spingboot 配置多个数据源报错
    Address already in use: JVM_Bind 端口被占用的几个解决办法
    数据库问题(一)
  • 原文地址:https://www.cnblogs.com/SeanOcean/p/11268638.html
Copyright © 2011-2022 走看看