• 【数据结构】树状数组


    树状数组主要用于解决查询修改等区间操作的问题。

    其实它也是线段树的一部分:线段树能做的,树状数组不一定能做;树状数组能做的,线段树一定能做(可能会比较慢)。

    那么,树状数组的优点:

    1.代码简洁好记。

    2.由于使用位运算,跑得快。

    当然也有缺点:

    1.相对来说难以理解。

    2.解决问题的广度不如线段树。

    这个算法到底是怎样的?

    树状数组的核心是lowbit。

    lowbit是一个函数,lowbit(x)代表着x的二进制中从右往左数第一个1以及1之后的0组成的二进制数。

    lowbit可以用简单的一行代码实现:

    lowbit(x) = x*(-x)

    至于具体原理,在此不表。

    我们利用lowbit来将一个数组分成许多块,每一块维护着这个区间的信息。(分块?)

    这里有一张图:

    其中,红色线左边的棕色线代表这个元素维护的区间(块)。

    比如,1的二进制是1,那么 lowbit(1) = 1,于是它维护 1 这个区间。

    比如,4的二进制是100,那么 lowbit(4) = 4,于是它维护 1~4 这个区间。

    比如,6的二进制是110,那么 lowbit(6) = 2,于是它维护 4~6这个区间。

      . . . . . .

    发现两个小规律:单数总是维护长度为1的块。原因也很简单,因为单数的二进制最右边一位永远是1,因此它们维护的长度也是1 。

    而2的次方维护的总是以它自身为长度的区间,原因也很简单,因为2的次方在二进制中意味着最高一位是1,其余全是0 。

    所以,形如1000000···这样的二进制,代表了从前往后到这个数所有元素,而1100000···代表了它的一半,1010000···则代表了它的1/4,以此类推,1000···1正好是个单数,维护了最小的区间,然后在它之上的1000···10则维护这个数本身和1000···1 。对半维护区间,是不是让你想起了线段树?而线段树是一个二叉树,意味着树状数组也是一个二叉树。只要画出它的右儿子,整棵树正是一棵二叉树:

     

    蓝线是加上的右儿子。

    因此,树状数组相对于线段树减少了一半的空间。

    那么我们看代码:

    代码:

     1 #include<iostream>
     2 #include<vector>
     3 #include<cstdio>
     4 #include<queue>
     5 #include<map>
     6 #include<cstdlib>
     7 #include<cmath>
     8 #include<algorithm>
     9 #include<set>
    10 #include<cstring>
    11 using namespace std;
    12 typedef long long ll;
    13 const ll INF=99999999;
    14 const int MAXN=500002;
    15 inline int lowbit(int x){
    16     return x&(-x);
    17 }
    18 int n,m;
    19 int a[MAXN];
    20 int t[MAXN*4];
    21 void add(int p,int x){
    22     for(int i=p;i<=n;i+=lowbit(i)){
    23         //将之后的数组也更新,所以是+=lowbit
    24         t[i]+=x;
    25     }
    26 }
    27 int query(int p){
    28     int re=0;
    29     for(int i=p;i>0;i-=lowbit(i)){
    30         //查询从起始到p的和
    31         re+=t[i];
    32     }
    33     return re;
    34 }
    35 int main()
    36 {
    37     ios_base::sync_with_stdio(false);
    38     cin.tie(0);
    39     
    40     cin>>n>>m;
    41     for(int i=1;i<=n;i++){
    42         cin>>a[i];
    43     }
    44     for(int i=1;i<=n;i++){
    45         //枚举每一个输入
    46         for(int j=i-lowbit(i)+1;j<=i;j++){
    47             //从比i小lowbit(i)的地方(这里要+1,至于为什么 反正这样写是对的)
    48             t[i]+=a[j];
    49             //构造数组
    50         }
    51     }
    52     
    53     for(int i=1;i<=m;i++){
    54         int a,b,c;
    55         cin>>a>>b>>c;
    56         if(a==1){
    57             add(b,c);
    58         }
    59         if(a==2){
    60             cout<<query(c)-query(b-1)<<endl;
    61             // 从后面-前面的,但是注意一定减后面的时候要-1
    62             // 举个例子,从1到1,如果是query(1)-query(1)的话就是0了
    63         }
    64     }
    65     return 0;
    66 }
    点击显示神奇代码

    1.单点加入

    void add(int p,int x){
    	for(int i=p;i<=n;i+=lowbit(i)){
    		t[i]+=x;
    	}
    }
    

     注意从编号开始,是每次+=lowbit(i)操作。另外,t[i]是+=而不是=。

    之所以每次加上lowbit能够“到达”下一个区间,原因在于树状数组本质上是一个二叉树。我们知道lowbit(i)代表这个元素维护的区间长度,加上自己为什么就能到达下一个区间呢?

    原因是树状数组是一个左右相等的二叉树(不知道怎么说,原谅我的不严谨),所以左儿子加上它自己相当于到达了它的父亲。它的父亲维护两倍于它的区间。 

    2.区间查询

    int query(int p){
    	int re=0;
    	for(int i=p;i>0;i-=lowbit(i)){
    		re+=t[i];
    	}
    	return re;
    }
    

      注意从p~1,是每次-=lowbit(i)。

    从最右边往前跳。这里也可以使用 i != 0,原因是每次减去自身区间长度,最后一定会到达第0个元素。这是我们已经统计完毕,可以退出了。

    3.初始化

    for(int i=1;i<=n;i++){
    		for(int j=i-lowbit(i)+1;j<=i;j++){
    			t[i]+=a[j];
    		}
    	}
    

      注意从i-lowbit(i)+1开始,j<=i;t[i]是+=。(i枚举每一个未处理的线性元素)

    对于每一个区间加上区间加上其中的元素。

    4.别忘了lowbit

    inline int lowbit(int x){
    	return x&(-x);
    }
    

      这里使用内联函数(inline)会更好。

    5.求区间值

    求a到b的区间和的时候注意应该是query(b)-query(a-1)

    query(b)-query(a-1)
    

      

  • 相关阅读:
    Soldier and Number Game素数筛
    HDU1501Zipper字符串的dfs
    HDU1285 确定比赛名次 拓扑排序模板题
    HDU1595 find the longest of the shortest dijkstra+记录路径
    HDU1556 Color the ball 前缀和/线段树/树状数组
    Function Run Fun递归+细节处理
    数学公式
    日常 java+雅思+训练题1
    HDU1423Greatest Common Increasing Subsequence
    HDU1595find the longest of the shortestdijkstra+记录路径
  • 原文地址:https://www.cnblogs.com/dudujerry/p/9858169.html
Copyright © 2020-2023  润新知