数据结构——树状数组

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

树状数组

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.线段树 点修改,区间修改

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值