zoukankan      html  css  js  c++  java
  • 【数据结构之树状数组】从零认识树状数组

    一、关于树状数组

    树状数组(Binary Indexed Tree,简称BIT),是一种修改和查询复杂度都为O(logN)的数据结构。但树状数组仅支持单点修改,在查询时,树状数组也要求被查询的区间具有可区间加减的性质。不过,树状数组由于代码实现容易、占用空间小,常用于代替线段树。

    二、详解树状数组

    在这里,我们定义原序列为a,树状数组为c,则有:

    其中,k为i的二进制表示中末尾0的个数,例如:i=3(101)时,k=0;i=8(1000)时,k=3。

    定义函数lowbit(x)=2k(k为x的二进制表示中末尾0的个数),利用机器补码的性质,得到:

    1 int lowbit(int x)
    2 {
    3      return x&(-x);
    4 }

    根据定义,我们可以列出(这里我们用a[i,j]表示a[i]~a[j]间的所有元素的信息):

    c[1]=a[1]

    c[2]=a[1,2]

    c[3]=a[3]

    c[4]=a[1,4]

    c[5]=a[5]

    c[6]=a[5,6]

    c[7]=a[7]

    c[8]=a[1,8]

    c[9]=a[9]

    c[10]=a[9,10]

    ……

    首先,由树状数组的定义,我们可以得到一个性质:

    a[x]在树状数组中第一次出现在c[x],并且c[x]包含的区间右端点为x

    此外,通过观察,我们可以发现:

    a[x]仅对c[x]、c[x+lowbit(x)]、c[x+lowbit(x)+lowbit(x+lowbit(x))]……产生影响

    类似于二进制拆分的思想,我们还可以发现:

    c[1]~c[x]可以还原出a[1]~a[x]的所有信息

    所以,树状数组的空间复杂度就为O(n)。

    那么,我们就可以写出维护树状数组的代码(这里我们的树状数组查询的信息为区间的和):

    1 void update(int x,int val)
    2 {
    3      for(;x<=n;x+=lowbit(x))
    4          c[x]+=val;
    5      return;    
    6 }

    接下来,如果我们要求区间a[i,j]的和,我们又要怎么做呢?

    不妨这样想,a[i,j]的和其实等于a[1,j]的和减去a[1,i-1]的和,那么我们只需要知道如何求一个的序列前缀和就可以了。

    首先,根据定义,我们知道:

    c[x]包含的区间长度为lowbit(x)

    在这个性质下,与维护操作类似,我们可以很轻松地写出求前缀和的代码:

    1 int query(int x)
    2 {
    3      int ans=0;
    4      for(;x;x-=lowbit(x))
    5          ans+=c[x];
    6      return ans;
    7 }

    以上,就是树状数组的基本内容了,下面我们来看一道例题。

    三、题目

    Description

    一行N个方格,开始每个格子里都有一个整数。现在动态地提出一些问题和修改:提问的形式是求某一个特定的子区间[a,b]中所有元素的和;修改的规则是指定某一个格子x,加上或者减去一个特定的值A。现在要求你能对每个提问作出正确的回答。1≤N<100000,,提问和修改的总数m<10000条。

    Input Description

    输入文件第一行为一个整数N,接下来是n行n个整数,表示格子中原来的整数。接下一个正整数m,再接下来有m行,表示m个询问,第一个整数表示询问代号,询问代号1表示增加,后面的两个数x和A表示给位置X上的数值增加A,询问代号2表示区间求和,后面两个整数表示a和b,表示要求[a,b]之间的区间和。

    Output Description

    共m行,每个整数

    Sample Input

    6

    3

    4

    1 3 5

    2 1 4

    1 1 9

    2 2 6

    Sample Output

    22

    22

    Data Size & Hint

    1≤N≤100000, m≤10000 。

    附上原题链接→_→|1080 线段树练习|CODEVS,算法爱好者社区

    虽然是线段树练习,但在开头我们便讲过,树状数组在一定情况下可以替代线段树。

    四、代码实现

    以下内容是一个树状数组单点维护并求区间和的模板,可以通过CodeVs1080。其实核心代码都在上文出现过,这里只是做一个整理。

     1 #include<cstdio>
     2 const int MAXN=1e5+10;
     3 int n,m;
     4 int lowbit(int x){return x&(-x);}
     5 int BIT[MAXN];
     6 void update(int x,int val)
     7 {
     8     for(;x<=n;x+=lowbit(x))
     9         BIT[x]+=val;
    10 }
    11 int query(int x)
    12 {
    13     int ans=0;
    14     for(;x;x-=lowbit(x))
    15         ans+=BIT[x];
    16     return ans;
    17 }
    18 int main()
    19 {
    20     scanf("%d",&n);
    21     for(int i=1;i<=n;++i)
    22     {
    23         int a;
    24         scanf("%d",&a);
    25         update(i,a);
    26     }
    27     scanf("%d",&m);
    28     for(int i=1;i<=m;++i)
    29     {
    30         int flag,l,r;
    31         scanf("%d%d%d",&flag,&l,&r);
    32         if(flag==1)update(l,r);
    33         else 
    34         {
    35             int ans=query(r)-query(l-1);
    36             printf("%d
    ",ans);
    37         }
    38     }
    39     return 0;
    40 }
    CodeVs1080 线段树练习

    弱弱地说一句,本蒟蒻码字也不容易,转载请注明出处http://www.cnblogs.com/Maki-Nishikino/p/6217811.html

  • 相关阅读:
    Python操作SQLServer示例
    T-SQL 谓词和运算符
    T-SQL 语句—— 游标
    SQL中的循环、for循环、游标
    web自动化快速入门
    接口自动化的总结
    jenkins知识
    SVN知识
    random.sample函数
    项目实战(六)
  • 原文地址:https://www.cnblogs.com/Maki-Nishikino/p/6217811.html
Copyright © 2011-2022 走看看