树状数组

树状数组基本用来维护前缀和,相比于数组(查询O(n),修改O(1)),前缀和数组(查询O(1),修改O(n)),其可以做到查询和修改均为logn,因此对于多组查询和修改复杂度为mlongn,远远快于前两者mn的复杂度。
其基本思想在于一个数可以唯一进行2进制分解,我要求1 - n这n个数的和,设n = 2^ (i1) + 2 ^ (i2) + … + 2 ^ (ik),其中i1 < i2 < … < ik。我只要维护长度为2 ^ (i1)区间的和,长度为2 ^ (i2)区间的和…最终这些区间总长度就是n,所有区间的和加起来就是1 - n的和。
那么如何划分使得区间之间没有重复呢?例如12的二进制为1100,我们可以这样划分[1-8],[9-12],这样逐个向下减就每个区间就不会重复。
进一步我们可以维护这样一个数组c,c[x]维护的是以x为右端点,长度为lowbit(x)区间的和,这样1-12的和我们就可以通过c[12] + c[8]求得,其中c[12]就是[9-12]的和,c[8]就是[1-8]的和。
于是就有

int ask(int x) {
	int ans = 0;
	for (; x; x -= x & -x) {
		ans += c[x];	
	}
	return ans;
}

那么我们怎么得到这样的c数组呢?我们可以通过树状数组的修改操作用nlogn的时间预处理出c数组。
下面我们来看一下修改操作,我们只要找出包含当前所要修改节点的节点即可,为了便于理解,我们先来看一下如何找到一个父节点的所有子节点。以8为例,c[8]表示以8为右端点,长度为8的序列和,首先我们先把8减1,即树状数组编号为8的节点肯定包含a[8],此时二进制变为0111,序列长度变为7,这时我们不难发现剩下的子节点应该是0111(7), 0110(6), 0100(4),于是c[8] = a[8] + c7 + c[6](a[5] + a[6])+ c[4](a[1] + a[2] + a[3] + a[4])。
再比如对于12(1100),它的所有子节点应该为a[12], c[11], c[10]。
总结一下:如何由父节点找子节点呢?设父节点编号为x,如果x & 1,就是a[x],否则就先将x减1,变成…011…1的形式,每次去掉最后一位1,直到变为…010…0为止。
反过来若当前节点的二进制表示为0…01…,我们可知其父节点为0…10…,于是每个节点的父节点唯一,这也就说明了为什么这种数据结构被称为树状数组,(用数组来存,是一种树结构)。这样我们只需要每次加一个lobit(x)即可实现。

void add(int x, int y) {
	for (; x <= n; x += x & -x) c[x] += y;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值