树状数组性质与操作

在学习树状数组前,需要了解lowbit运算.
什么是lowbit运算呢?
lowbit(x)表示返回当前十进制x中,其二进制形式下低位处最先遇到1时,1所表示的值

如 10100 , 低位处最先遇到1在二进制的第三位,则保留后三位, 100 转化位十进制为 4
我们来算出前8的lowbit值
1 -> 1B 0> 1
2 -> 10B -> 10B -> 2
3 -> 11B -> 1B - > 1
4 -> 100B -> 100B -> 4
5 -> 101B -> 1B -> 1
6 -> 110B -> 10B -> 2
7 -> 111B -> 1B -> 1
8 -> 1000B -> 1000B -> 8

所以有如下关系 lowbit(x) = x & -x; 前人智慧真伟大
如 3 & -3 [011]原 & [101]原 -> (001)2 = 1

那么有什么用呢?
如图所示:

在这里插入图片描述
数组A存放的是原数据
数组C经过lowbit循环运算得到的域a之间的关系,形成一颗树状的关系,故名思意为树状数组
这张图的规律如下:
C[1] 其lowbit后为 1 对应 a[1]数组
C[2] 其lowbit运算后为 2 对应 a[1] a[2]
。。。。。

所以我们可以知道 在树状数组中,下标为i所对应原数组的关系有:
1)i,为奇数时,对应原数组下标i,只包含一个节点
2)i,为偶数不为2的n次幂时,只包含俩个节点
3)i为2的n次幂时,与当前下标i前的所有原数组有关

树状数组的一般操作有:
1)单点查询(一般数组也可以)
2)单点修改(一般数组也可以)
3)维护序列前缀和/区间修改 (性质最重要) c[x] 维护的是 ∑ i = x − l o w b i t ( x ) + 1 x a [ i ] \sum_{ i=x-lowbit(x)+1}^x a[i] i=xlowbit(x)+1xa[i]
4) 区间查询前缀和(性质同样重要)

区间查询和区间修改的时间复杂度均为O(logn)!!! !!! !!!
这就是为为什么树状数组存在的重要原因

维护前缀和代码如下:

void add(int x,int y)
{
	//对区间[x,n] 之间的区间+ y
	for(; x <=n ; x+=lowbit(x)) c[x] += y;
}

区间查询代码如下;

int ask(int x)
//ask询问的是 区间[1,x]之间的和,注意 c存放的只是n次修改后的增值,不是原值,原值保持不变的
{
	int ans = 0;
	for(;x;x-=lowbit(x)) ans += c[x];
	return ans;
}

常用操作一:
单点增加如a[x]+d
只需要add(x,d)

对区间[L , R] + d
add(L,d) - add(R+1,-d)

查询经过n次操作后a[x] 的值
a[x] = a[x] + ask(x)

同时也可以查询j经过n次操作后 a[L,R]的区间和
我们来看下假设对a1 ,a2, a3, a4, a5, a6 … an之间进行了n次操作
a[1] 增长值:c[1]
a[2] 增长值:c[1] + c[2]
a[3] 增长值:c[1] + c[2] + c[3]
a[4] 增长值:c[1] + c[2] + c[3] + c[4]

a[x] 增长值:c[1] + c[2] + c[3] + c[4] +…+c[x]
a[1] ~ a[x] 总增长值: x * c[1] + (x-1) * c[2] + (x-2)* c[3] + (x-3) * c[4] + … 1 *c[x]

推出操作改动后的增值公式为:a[1] ~ a[x] 总增长值 : Σ (x-i+1) * c[i] = (x+1)Σc[i] - Σi * c[i] (1 <= i <= x)

初始值的前缀和
sum[i]表示前 1 ~ i 的前缀和
sum[l,r] = sum[r] - sum[l-1];

n次操作后的sum[l,r]的区间和为

ans[l,r] = sum[l,r] + a[l] ~ a[r] 的总增长值
= ( sum[r] + (r+1) * Σc[i] - Σi * c[i] ,(l<=i <=r))
- (sum[l-1] + (l-1+1) * Σc[i] - Σi * c[i] ,(1 <= i <= l-1 ))
在(l<=i <=r)
ask0( r) = Σc[i]
ask1( r) = Σi * c[i]

在(1 <= i <= l-1 )
ask0(l-1) = Σc[i]
ask1(l-1) = Σi * c[i]

ans[l,r] = (sum[r] + (r+1)*ask0( r) - ask1(l-1) )
- (sum[l-1] + (l-1+1)*ask0(l-1) - ask1(l-1))

如何维护 Σc[i] , Σ (i * c[i]) ( l <= i <= r)

使用add操作
一对 add0(l,d),add0(r+1,-d) 表明 算出了 c[l,r]的改动
一对 add1(l,l*d) add1(r+1,-(r+1)*d) 表明算出了 Σ (i * c[i])[l,r] 上的改动

令 c[0][i] 表示 c[i] 的单点操作
c[1][i] 表示 (i * c[i]) 的单点操作

操作二:可用树状数组求序列 a 的逆序对,前提是a数组内的数据不是特别大
令cnt数组表示a序列中最大数据 到 1之间的出现次数

add(a[x],1) 表示 在a序列前x个数中 a[x] 数据存在次数+1
ask(a[x] - 1) 表示 在 当前序列a[x] 下 1~a[x] - 1 在序列 a下标 x 之前出现的次数
表明序列a位置x之前有多少个数小于a[x]
maxn表示序列a中最大的值 则 ask(maxn) - ask(a[x]) 表示在前x个序列中有多少个大于a[x]的数

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值