树状数组1

1 介绍

给出一个长为 n n n 的数组 a a a,进行若干次几下操作:

  1. 改变 a x a_x ax 的值
  2. ∑ i = 1 x a i \sum_{i=1}^xa_i i=1xai 并输出

树状数组是一种树形结构的数组,以二叉树的方式进行存储。它的基本操作有:改变元素的值、求区间前缀和。

树状数组的优越性在于其进行操作的复杂度都是 O ( n l o g n ) O(nlogn) O(nlogn).

2 前置内容

求一个数的二进制从右往左第一个 1 \texttt 1 1 与后面末尾 0 \texttt 0 0 组成的数 lowbit().

例如: l o w b i t ( 4 ) = 4 , l o w b i t ( 7 ) = 1 lowbit(4)=4,lowbit(7)=1 lowbit(4)=4,lowbit(7)=1.

实现方法:

void lowbit(int n) {
    return n & (-n);
}
//other examples
//return n & (~n + 1);
//return n & (n ^ (n - 1));

3 实现

以一个长度为 8 8 8 的数组 a a a 举例:

现将数组 a a a 的所有元素都放到二叉树的叶子结点上,然后进行操作,得到树状数组 c c c.

c 1 = a 1      ( 1 ) 10 = ( 0001 ) 2 c_1=a_1 \ \ \ \ (1)_{10}=(0001)_2 c1=a1    (1)10=(0001)2

c 2 = a 1 + a 2      ( 2 ) 10 = ( 0010 ) 2 c_2=a_1+a_2 \ \ \ \ (2)_{10}=(0010)_2 c2=a1+a2    (2)10=(0010)2

c 3 = a 3      ( 3 ) 10 = ( 0011 ) 2 c_3=a_3 \ \ \ \ (3)_{10}=(0011)_2 c3=a3    (3)10=(0011)2

c 4 = a 1 + a 2 + a 3 + a 4      ( 4 ) 10 = ( 0100 ) 2 c4=a_1+a_2+a_3+a_4 \ \ \ \ (4)_{10}=(0100)_2 c4=a1+a2+a3+a4    (4)10=(0100)2

c 5 = a 5      ( 5 ) 10 = ( 0101 ) 2 c_5=a_5 \ \ \ \ (5)_{10}=(0101)_2 c5=a5    (5)10=(0101)2

c 6 = a 5 + a 6      ( 6 ) 10 = ( 0110 ) 2 c_6=a_5+a_6 \ \ \ \ (6)_{10}=(0110)_2 c6=a5+a6    (6)10=(0110)2

c 7 = a 7      ( 7 ) 10 = ( 0111 ) 2 c_7=a_7 \ \ \ \ (7)_{10}=(0111)_2 c7=a7    (7)10=(0111)2

c 8 = a 1 + a 2 + a 3 + a 4 + a 5 + a 6 + a 7 + a 8      ( 8 ) 10 = ( 1000 ) 2 c_8=a_1+a_2+a_3+a_4+a_5+a_6+a_7+a_8 \ \ \ \ (8)_{10}=(1000)_2 c8=a1+a2+a3+a4+a5+a6+a7+a8    (8)10=(1000)2

二叉树如下:

我们可以发现:

从下往上第 i i i 层的编号二进制的末尾都是 ( i − 1 ) (i-1) (i1) 0 \texttt 0 0,也就是从右往左第一个 1 \texttt 1 1 所在第 i i i 位。

如果要改变某个元素的值( a x + y a_x+y ax+y),如 a 3 a_3 a3 ,那么同样要改动 c 3 , c 4 , c 8 c_3,c_4,c_8 c3,c4,c8 的值。观察这三个元素下标的规律,我们可以发现:

3 + l o w b i t ( 3 ) = 4 , 4 + l o w b i t ( 4 ) = 8 3+lowbit(3)=4,4+lowbit(4)=8 3+lowbit(3)=44+lowbit(4)=8

可得代码:

void change(int x, int y) {
    for (int i = x;i <= n; i += lowbit(i)) c[cnt] += y;
}

如果要求 ∑ i = 1 x a i \sum_{i=1}^x a_i i=1xai,如求从 a 1 a_1 a1 a 7 a_7 a7 的和,那么可以通过 c 4 + c 6 + c 7 c_4+c_6+c_7 c4+c6+c7 得到。观察这三个元素下标的规律,我们可以发现:

6 = 7 − l o w b i t ( 7 ) , 4 = 6 − l o w b i t ( 6 ) 6=7-lowbit(7),4=6-lowbit(6) 6=7lowbit(7),4=6lowbit(6)

可得代码:

int count(int x) {
    int res = 0;
    for (int i = x;i >= 1; i -= lowbit(i)) res += c[i];
    return res;
}

如果要求 ∑ i = l r a i \sum_{i=l}^r a_i i=lrai,利用前缀和, ∑ i = 1 r a i − ∑ i = 1 l − 1 a i \sum_{i=1}^ra_i-\sum_{i=1}^{l-1}a_i i=1raii=1l1aicount(r) - count(l)即可。

4 离散化

存储大数的方法,例如:

1,10000000,99999999,12345,64678

将它们排序得到:

1,12345,64678,10000000,99999999

对他们标号,依次对应:

1,2,3,4,5

可以利用 map 来保存编号。

代码如下:

for (int i = 1; i <= n; i++)
	b[i] = a[i];
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i++) 
    mp[b[i]] = i;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值