学树状数组?这篇就够(入门)了!

学树状数组?这篇就够(入门)了!

在这里插入图片描述
有一天,小明给了我三个问题其实是我自己出的啦

(1)
有一个机器,支持两种操作,在区间[1,10000]上进行。
操作 A \rm A A:把位置 x \rm x x的值+ k \rm k k
操作 B \rm B B:询问区间 [ l , r ] \rm [l,r] [l,r]所有数字之和
区间的初始值全部为 0 \rm 0 0
现在你要充当这个机器,操作 A \rm A A和操作 B \rm B B会被穿插着安排给你,
要求对于所有操作 B \rm B B,给出正确的答案。
怎样做才能最节省精力?

(2)
有一个机器,支持两种操作,在区间 [ 1 , 10000 ] [1,10000] [1,10000]上进行。
操作 A \rm A A:把区间 [ l , r ] [l,r] [l,r]的值全都+ x \rm x x
操作 B \rm B B:询问 x \rm x x位置的值。
区间的初始值全部为 0 \rm 0 0
现在你要充当这个机器,操作 A \rm A A和操作 B \rm B B会被穿插着安排给你,
要求对于所有操作 B \rm B B,给出正确的答案。
怎样做才能最节省精力?

三个问题中操作的数量都可以认为是10000这个数量级

你可以动用的工具有:无限墨水的笔,一张足够大的纸,你的大脑(没多大内存的~)。

注意:

  1. 举个例子,进行这种类似的操作:
    从一行任意打乱的数字中找一个数字
    不能认为一瞬间就可以找到,在这里所花费的精力和数字的总数具有线性关系。
  2. 我们认为将数据转换为二进制不需要任何时间。

对于问题 1 \rm 1 1,如果我们每种操作都暴力进行,
那么显然总的时间复杂度为 O ( m A + n ∗ m B ) \rm O(mA+n*mB) O(mA+nmB), n \rm n n表示区间长度,
m A \rm mA mA表示操作 A \rm A A执行的次数, m B \rm mB mB表示操作 B \rm B B执行的次数。
那么有没有一种更加轻松的办法呢?
我们将引入一种数据结构,叫做树状数组

在介绍树状数组之前,需要先介绍一下二进制的按位运算,这里只需要用到两种:

按位与运算,符号&: 两个数字都为1时,得1,否则得0.

那么 1 1 1& 1 1 1 = = = 1 1 1, 0 0 0& 1 1 1 = = = 0 0 0, 1 1 1& 0 0 0 = = = 0 0 0, 0 0 0& 0 0 0 = = = 0 0 0

3 3 3& 11 11 11的值是多少呢?我们把它化成二进制

两个数字分别为 0011 0011 0011 1011 1011 1011,然后对应的,每位之间进行与运算

    0011 ~~~0011    0011
& 1011 1011 1011
— — — — — — ——————
    0011 ~~~0011    0011

所以答案是 0011 0011 0011,即十进制的 3 3 3

接下来再介绍一下按位非运算,符号~,运算方法是0变1,1变0

比如 ~ 9 9 9的值,就是~ 1001 1001 1001(二进制),得到 0110 0110 0110

那么按位运算就说完了。

最后为了方便理解后面的内容,还得介绍一个计算机的特点。

在计算机中,我们操作的变量通常都有一个固定的位数,

比如 c \rm c c++中 i n t \rm int int 32 32 32_ t \rm t t 类型的变量,它用 32 32 32位的二进制数来存储一个整数。

在这个范围下,

~ 1 1 1 =~ 00000000000000000000000000000001 00000000000000000000000000000001 00000000000000000000000000000001= 11111111111111111111111111111110 11111111111111111111111111111110 11111111111111111111111111111110

另外, n \rm n n位的二进制数进行运算,一旦向第 n \rm n n+ 1 1 1位有进位,会直接舍去这个进位,

如四位二进制数 1111 1111 1111+ 0001 0001 0001,答案是 0000 0000 0000而不是 10000 10000 10000

有了这么多铺垫,要开始正题啦~

现在就要介绍一个非常神奇的函数,它叫做lowbit。

l o w b i t ( x ) \rm lowbit(x) lowbit(x)= x \rm x x& ( ( (( ((~ x ) \rm x) x)+ 1 ) 1) 1)(为了少引入补码的概念,我们这里稍微麻烦了一下,其实 x \rm x x&- x \rm x x就行)

它的作用是什么呢?

它只保留"从低位向高位数,第一个数字1"作为运算结果

比如二进制数 00011100 00011100 00011100,结果就是 00000100 00000100 00000100,也就是 4 4 4

11111001 11111001 11111001,结果就是 00000001 00000001 00000001,也就是 1 1 1

不信的话可以验证一下。

那么这种运算对我们的算法有什么帮助呢?

首先我们来解决一下问题1。

先列举出从1~32的lowbit,

1   2   1   4   1   2   1   8   1   2   1   4   1   2   1   16   1   2   1   4   1   2   1   8   1   2    1   4   1   2   1   32 1 ~2 ~1 ~4 ~1~ 2 ~1 ~8 ~1 ~2 ~1 ~4 ~1 ~2 ~1 ~16 ~1 ~2 ~1 ~4 ~1 ~2 ~1 ~8 ~1 ~2~ ~1 ~4 ~1 ~2 ~1 ~32 1 2 1 4 1 2 1 8 1 2 1 4 1 2 1 16 1 2 1 4 1 2 1 8 1 2  1 4 1 2 1 32

我们让第 i \rm i i个位置管理 [ i − l o w b i t ( i ) + 1 , i ] \rm [i-lowbit(i)+1,i] [ilowbit(i)+1,i]这一段区间,示意图如下:

怎么看每个数字管理多少?

只要顺着数字往下画竖线,碰到的第一根横线所覆盖的范围就是它能管理的范围。

我们每次执行操作 A \rm A A(把位置 x \rm x x的值+ k \rm k k),只需要把"能管理到 x \rm x x的所有位置"都+ k \rm k k就行

那么怎样快速找到哪些位置能管理到x呢?

答案还是 l o w b i t \rm lowbit lowbit

我们先更新 x \rm x x,然后把 x \rm x x赋给一个新值, x + l o w b i t ( x ) \rm x+lowbit(x) x+lowbit(x),那么新值依然可以管理到 x \rm x x ,这样依次类推直到

x \rm x x> 10000 10000 10000即可。

比如 x = 2 \rm x=2 x=2,那么首先把 2 2 2的值 + k \rm +k +k,这不用说。

然后 x \rm x x的新值= x \rm x x+ l o w b i t ( x ) \rm lowbit(x) lowbit(x)= 2 2 2+ l o w b i t ( 2 ) \rm lowbit(2) lowbit(2)= 4 4 4,对着上面的示意图看看,会发现 4 4 4确实能管理到 2 2 2,那么把 4 4 4的位置+ k \rm k k

然后再来一遍, x \rm x x= 4 4 4+ l o w b i t ( 4 ) = 8 \rm lowbit(4)=8 lowbit(4)=8,发现 8 8 8还是能管理到 2 2 2,继续给 8 8 8这个位置 + k \rm +k +k,就这样依次类推下去

直到x = 16384 =16384 =16384时,超过10000了,操作完成。

这样操作之后,树状数组里每一位当前存的值可能并不是该位置的实际值,为了方便区分,在下文中我们把实际值叫做"原数组的值",当前值就叫做"树状数组的值"。

可以证明,对于任意一个 x \rm x x属于[1,10000]我们最多进行 l o g \rm log log ( 2 , 10000 ) (2,10000) (2,10000)次操作,就可以完成操作A

那么把操作A变复杂,从 O ( 1 ) \rm O(1) O(1)变到 O ( l o g n ) \rm O(logn) O(logn)能换来什么好处?

答案就是,可以把操作 B \rm B B的时间复杂度降低成 l o g \rm log log级别的

询问区间 [ L , R ] \rm [L,R] [L,R]的和 s u m ( L , R ) \rm sum(L,R) sum(L,R)。我们只需要求出 s u m ( 1 , R ) \rm sum(1,R) sum(1,R) s u m ( 1 , L − 1 ) \rm sum(1,L-1) sum(1,L1),

然后 s u m ( 1 , R ) \rm sum(1,R) sum(1,R)- s u m ( 1 , L − 1 ) \rm sum(1,L-1) sum(1,L1)就是 s u m ( L , R ) \rm sum(L,R) sum(L,R)

那么对于任意的 x \rm x x s u m ( 1 , x ) \rm sum(1,x) sum(1,x)怎么求呢?

我们把最终得到的答案存在 a n s \rm ans ans变量中,执行下面的操作:

(1) a n s \rm ans ans初始化为 0 0 0

(2) a n s \rm ans ans加上 x \rm x x位置的值

(3)给 x \rm x x赋予新值 x \rm x x- l o w b i t ( x ) \rm lowbit(x) lowbit(x)

(4)如果 x > 0 \rm x>0 x>0则跳回操作 ( 2 ) (2) (2),否则结束算法。

举个例子介绍一下:

一开始我们还是停留在树状数组第 x \rm x x位置上(比如 x \rm x x= 6 \rm 6 6吧),答案一开始为 0 \rm 0 0

还记得吗,我们在进行"给原数组第 x \rm x x位置的数增加k"这个操作时,把"能管理到 x \rm x x的所有位置"都增加了k。

那么,对于任意一个位置,树状数组里的值就是"它能管理到的所有位置上,原数组的值之和"。

因此我们给答案加上树状数组第 x \rm x x位置的值,这里就得到了 s u m ( 5 , 6 ) \rm sum(5,6) sum(5,6),因为 6 \rm 6 6能管理 [ 5 , 6 ] \rm [5,6] [5,6]

然后给 x \rm x x减去 l o w b i t ( x ) \rm lowbit(x) lowbit(x),得到 4 \rm 4 4。再加上 x \rm x x位置的值,也就是 s u m ( 1 , 4 ) \rm sum(1,4) sum(1,4),因为 4 \rm 4 4能管理 [ 1 , 4 ] \rm [1,4] [1,4]

再让 x \rm x x= x \rm x x- l o w b i t ( x ) \rm lowbit(x) lowbit(x),得到0,由于不再大于0,算法终止,得到答案。

这时答案恰好是 s u m ( 1 , 6 ) \rm sum(1,6) sum(1,6),哈哈~

依然可以证明,最多只需要进行 l o g \rm log log级别次数的查询。

这样我们进行操作B的时间复杂度也是 l o g \rm log log级别了。

至此,树状数组就说完了,问题1也成功得到解决,时间复杂度 O ( ( m A + m B ) ∗ l o g n ) \rm O((mA+mB)*logn) O((mA+mB)logn)

10000 \rm 10000 10000这个数量级下明显比之前的 O ( m A + ( m B ∗ n ) ) \rm O(mA+(mB*n)) O(mA+(mBn))小得多。

而且,位运算的常数非常小,因此整个算法执行速度会很快。

问题2怎么办?用差分的方法,区间 [ l , r ] \rm [l,r] [l,r]所有值+ k \rm k k改成"位置 l \rm l l加上 k \rm k k,位置 r \rm r r+ 1 \rm 1 1减去k"

查询的时候直接查询 s u m ( 1 , x ) \rm sum(1,x) sum(1,x)就行,不理解的话可以自己构造一组数据尝试一下。


注:本篇博客选自《树状数组详细讲解,不会算法也能看懂哦~》,有删减,已经过CSDN的优化处理

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值