该浏览器不支持canvas

浅谈树状数组

我们常常把树状数组和线段树相比较,一般认为树状数组的处理效率和编写复杂度优于线段树,线段树较树状数组能处理更多的信息,有人提到线段树擅长处理横向区间的问题,树状数组擅长处理纵向区间的问题,总之,在处理区间这类问题上,我们二者都要掌握。树状数组的建树复杂度为O(nlogn),维护和查询复杂度为O(logn)。

树状数组是什么呢?我们用两张图来表示一下:

换言之,就是根据数组下标二进制1的最低位构成的一棵变形树

我们用A数组来表示原始数组,C数组来表示树状数组

0001的最低位在2^0上,C[1]只表示A[1]

0010的最低位在2^1上,C[2]表示A[1]+A[2]

0011的最低位在2^0上,C[3]表示A[3]

0100的最低位在2^2上,C[4]表示A[1]+A[2]+A[3]+A[4] = C[2]+A[3]+A[4] 

0101的最低位在2^0上,C[5]只表示A[5]

0110的最低位在2^1上,C[6]表示A[5]+A[6]

换言之,在树状数组中一个下标最低位为2的几次幂,该下标就指代它往前(包括它)往前数2的几次幂的和

如果我们要计算1到6的和

我们需要计算C[4]+C[6]

学习树状数组时,有一个必须掌握的函数:lowbit

lowbit其实就是取出x的二进制的最低位1,比如0110这个二进制数,lowbit(6) = 2

具体实现如下

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

计算机中负数使用对应的正数的补码来表示
例如 : n=6(0110)
-n=-6=(1001+1)=(1010)
n&(-n)=(0010)=2=2^1

即最低位1在2^1的位置上

 

所以我们计算1到6的和时

就可以先算SUM += C[6]

再6 - lowbit(6) = 4

再算SUM += C[4]

4 - lowbit(14) = 0 结束运算

 1 int search(int n)
 2 {
 3     int sum = 0;
 4     while (n)
 5     {
 6         sum += c[n];
 7         n -= lowbit(n);
 8     }
 9     return sum;
10 }

 

在更新时方向则相反,我们假设原始数据是一个长度为6的数组

我需要更新下标为1的数据是,更新顺序为:

先更新C[1],1+lowbit(1) = 2

再更新C[2],2+lowbit(2) = 4

4+lowbit(4) = 8 > 6

更新停止

 

更新和初始化树状数组函数如下:

void update(int n, int pos, int val)
{
    while (pos <= n)
    {
        c[pos] += val;
        pos += lowbit(pos);
    }
}

void init(int n)
{
    memset(c, 0, sizeof(c));
    for (int i = 1; i <= n; i++)
        update(n, i, a[i]);
}

 

在学习树状数组时,一定时刻注意要用二进制的思想来去思考,而这一算法的优势之处也是在此

这里再附上求1到x和的模板:

 1 #include <iostream>
 2 #include <string.h>
 3 
 4 using namespace std;
 5 
 6 int a[1050];//原始数据
 7 int c[1050];//树状数组
 8 
 9 int lowbit(int n)
10 {
11     return n & -n;
12 }
13 
14 void update(int n, int pos, int val)
15 {
16     while (pos <= n)
17     {
18         c[pos] += val;
19         pos += lowbit(pos);
20     }
21 }
22 
23 void init(int n)
24 {
25     memset(c, 0, sizeof(c));
26     for (int i = 1; i <= n; i++)
27         update(n, i, a[i]);
28 }
29 
30 int search(int n)
31 {
32     int sum = 0;
33     while (n)
34     {
35         sum += c[n];
36         n -= lowbit(n);
37     }
38     return sum;
39 }
40 
41 int main()
42 {
43     ios::sync_with_stdio(false);
44     int n,m;
45     while (cin >> n >> m)
46     {
47         for (int i = 1; i <= n; i++)
48             cin >> a[i];
49 
50         init(n);
51 
52         while (m--)
53         {
54             int t;
55             cin >> t;
56             cout << search(t) << endl;;
57         }
58     }
59 
60     return 0;
61 }

如果还是有些懵的话,这里再推荐一个更详细的入门讲解:https://www.cnblogs.com/acgoto/p/8583952.html#4046752

后面我们再详谈求区间最值和逆序对的方法

posted @ 2018-09-08 16:27  真是啰嗦  阅读(439)  评论(0)    收藏  举报