树状数组 介绍 及应用 C++代码实现+模板+例题

 

1.问题来源

动态连续和查询问题。给定一个 n个元素的数组 A1,A2,...,An,你的任务是

设计一个数据结构,支持以下两种操作。

Addx,d)操作:让 Ax增加 d.

QueryL,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)假如 i0,算法结束,返回 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)步。

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

i+=lowbit(i)这个过程实际上是一个把末尾 1补为 0的过程。

两种应用

1.

单点修改,区间查询。

add(pos,x)->在下标 pos 处加上 x
query(pos)->返回 1 到 pos 的前缀和

那么查询区间 [ l , r ]的操作就是query( r ) - query( l - 1 )

2.

区间修改,单点查询

add 和 query 的操作同运用一,但是使用方式不同:
区间修改(给区间 [ l , r ]加上 d)->

add(l,d);
add(r+1,-d);

单点查询(查询 pos 的值)->

query(pos);

 模板

int lowbit(int x)    // 利用了神奇的二进制 特性 
{
	return x&(-x);
}
int add(int i,int x)   //  加数,往右走,肯定是左子节点, 
{
	for(;i<=n;)
	{
		d[i]+=x;
		i=i+lowbit(i);
	}
}
int sum(int c)          // 计算和,往左走 
{
	int s=0;
	while(c>0)
	{
		s+=d[c];
		c=c-lowbit(c);
	}
	return s;
}

例题:

士兵杀敌1

#include<iostream>
using namespace std;
#define range 1000005
int n,d[range];
int lowbit(int x)    // 利用了神奇的二进制 特性 
{
	return x&(-x);
}
int add(int i,int x)   //  加数,往右走,肯定是左子节点, 
{
	for(;i<=n;)
	{
		d[i]+=x;
		i=i+lowbit(i);
	}
}
int sum(int c)          // 计算和,往左走 
{
	int s=0;
	while(c>0)
	{
		s+=d[c];
		c=c-lowbit(c);
	}
	return s;
}
int main()
{
	int m,t1,t2;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&t1);
		add(i,t1);
	}
	for(int i=0;i<m;i++)
    {
    	scanf("%d %d",&t1,&t2);
    	printf("%d\n",sum(t2)-sum(t1-1));
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值