数据结构——树状数组

区间信息的维护与查询专题———

树状数组

1.问题来源

动态连续和查询问题。给定一个n个元素的数组A1,A2,...,An,你的任务是设计一个数据结构,支持以下两种操作。

★ Add(x,d)操作:让Ax增加d.

★ Query(L,R):计算AL+AL+1+...+AR.

对普通数组进行次修改或特定区间求和,时间复杂度为O(N),N为修改或求和需要扫描的数组区间大小。但有一种称为树状数组(又称二叉索引树)的数据结构可以很好的解决这个问题,时间复杂度则为O(logN)。加了一个log,学过数学的我们应该都知道效率优化有多大。

 

2.相关的定义与概念

在讲实现之前,我们需要先理解一个函数lowbit(x),这是一个自定义的函数,函数名是约定成俗的,作用就是x的二进制表达式中最右边的1所对应的值(而不是这个比特的序号)。比如,38288的二进制是1001010110010000,所以lowbit(38288)=16(二进制是10000)。

代码实现

int lowbit(int x)//位运算,利用计算机补码特性

{

    return x&-x;

}

写个长注释,假设x=10

10的二进制:1010,我们知道 -x就是x的二进制按位取反,末尾加一以后的结果 

-10的二进制:0101+1=0110 

然后位运算符&(按位与),1010&0110=10,十进制表示就是2 

所以lowbit(10)=2 

这个函数很重要,所以先在这里交代清楚原理。

 

下面上图,就正式开讲树状数组了。

下图是一棵典型的BIT,由15个结点组成,编号为1~15.

 

 

 

灰色结点是树状数组中的结点,每一层结点的lowbit相同,而且lowbit越大,越靠近根。对于结点i,如果它是左子结点,那么父结点的编号就是i+lowbit(i);如果它是右子结点,那么父结点的编号是i-lowbit(i).接着构造一个辅助数组C,其中

Ci=Ai-lowbit(i)+1+Ai-lowbit(i)+2+...+Ai

 换句话说,C的每一个元素都是A数组中的一段连续和。在BIT中,每个灰色结点i都属于一个以它自身结尾的水平长条(对于lowbit=1的那些点,“长条”就是那个结点自己),这个长条中的数之和就是Ci。

比如结点12的长条就是从9~12,即C12=A9+A10+A11+A12。同理,C6=A5+A6。这个等式极为重要,请大家花一些时间验证一下“Ci就是以i结尾的水平长条内的元素之和”这一事实。

有了C数组之和,计算前缀和Si就变得简单了。顺着结点i往左走,边走边“往上爬”(注意并不一定沿着树中的边爬),把沿途经过的Ci累加起来。就可以了。(请大家验证,沿途经过的Ci所对应的长条不重复不遗漏地包含了所有需要累加的元素),如下图所示。

 

而如果修改了一个Ai,需要更新C数组中的哪些元素呢?从Ci开始往右走,边走边“往上爬”(同样不一定沿着树中的边爬),沿途修改所有结点对应的Ci即可(请大家验证,有且仅有这些结点对应的长条包含被修改的元素),如下图所示。

 

 

说了那么多,大家发现了吗?树状数组其实就是利用二进制

 

3.伪代码实现

树状数组的第i个元素Tree[i]表示A[lowbit(i)+1..i]的和,其中lowbit(i)表示i的最低二进制位。

3.1当想查询一个A[1]+...+A[i]的和,可以依据如下算法:

(1)令sum=0,转第(2)步。

(2)假如i≤0,算法结束,返回sum值,否则sum+=Tree[i],转第(3)步。

(3)i-=lowbit(i),转第(2)步。

可以看出,这个算法就是将一个个区间的和全部加起来,并且i-=lowbit(i)这一步实际上等价于将i的二进制的最后一个1减去,而i的二进制里最多有logn个1,所以查询效率是O(logn).

3.2而给A[i]加上x的算法如下:

1)当i>n时,算法结束,否则转第(2)步。

2)Tree[i]+=x,i+=lowbit(i),转第(1)步。

i+=lowbit(i)这个过程实际上是一个把末尾1补为0的过程。容易看出复杂度也是O(logn)

 

 

4.模板题讲解

nyoj108士兵杀敌(一)

时间限制:1000 ms  |  内存限制:65535 KB

难度:3

描述

南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的。

小工是南将军手下的军师,南将军现在想知道第m号到第n号士兵的总杀敌数,请你帮助小工来回答南将军吧。

注意,南将军可能会问很多次问题。

输入

只有一组测试数据

第一行是两个整数N,M,其中N表示士兵的个数(1<N<1000000),M表示南将军询问的次数(1<M<100000)

随后的一行是N个整数,ai表示第i号士兵杀敌数目。(0<=ai<=100)

随后的M行每行有两个整数m,n,表示南将军想知道第m号到第n号士兵的总杀敌数(1<=m,n<=N)。

输出

对于每一个询问,输出总杀敌数

每个输出占一行

 

代码如下:

#include<cstdio>

int c[1000005];

int N,M;

int lowbit(int x)

{

return x&(-x);

}

 

void add(int i,int x)

{

while(i<=N)

{

c[i]=c[i]+x;

i=i+lowbit(i);

}

}

 

int sum(int i)

{

int sum=0;

while(i>0)

{

sum=sum+c[i];

i=i-lowbit(i);

}

return sum;

}

 

int main()

{

int i,j,temp,x,y;

scanf("%d%d",&N,&M);

for(i=1;i<=N;i++)

{

scanf("%d",&temp);

add(i,temp);

    }

    for(i=1;i<=M;i++)

    {

     scanf("%d%d",&x,&y);

     printf("%d\n",sum(y)-sum(x-1));//为什么这里是x-1

}

    return 0;

}

 

 

5.其他可供练习本算法的题目

nyoj 116 士兵杀敌(二)

nyoj 117 求逆序数

hdu 1166 敌兵布阵

hdu 1556 Color the ball

hdu 1394 Minimum Inversion Number

 

6.算法延伸

 

1.二维的树状数组

2.RMQ(范围最值问题)

3.线段树 点修改,区间修改

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值