树状数组再学——“通俗易懂,耐心赏读”

前言

就是说经历的太少,之前无法Get到树状数组的点并且一味的被灌输知识。这次来主动重新研究一遍树状数组究竟是什么?

  • 用于高效维护前缀信息
  • 需要满足信息的高效合并
    • 例子:两个子区间的最大值取Max就是整个区间的最大值
  • 维护区间信息需要满足信息可减
    • 例子:知道一个区间的和且知道其中一个子区间的和,就可以知道另一个区间的和
    • 反例:反之知道一个区间的最大值和一个子区间的最大值,并不能确定另一个子区间的最大值

lowbit

这是在树状数组中必须要会的东西,定义为:对于一个正整数 x ,定义lowbit(x)为 x 的二进制表示中最右边的1所对应的位置(值)。

即lowbit(x) = x&-x

比如:

  • 6=110₂ ->lowbit(6)=2
  • 4=100₂ ->lowbit(4)=3

为什么是对的?首先我们要介绍二进制的表示方式:原码、反码、补码

在正数中,原码反码补码都相同

例如用6位二进制表示一个数,那么6=000110₂

那-6的反码就是111001,负数的补码是反码+1,即-6=111010₂(反码)

这里还出现一个奇怪的现象,因为0和-0在反码中可以被重复表示

  • 111111₂=0
  • 000000₂=0

所以计算机中反码的表示范围会比补码少1个,为了实现资源利用最大化,计算机就会用补码来存数(反码是一个方便定义补码的螺丝钉呢)

那为什么 x&-x 是对的?

我们可以很容易想到,对于6=000110₂,它的反码是6=111001,补码是111010,相当于补码加的1正好可以使补码右边的1变成0,直到找到与原码从右往左数的第一个1的位置。即lowbit(6)=2

在树状数组中,节点 i 表示的区间为 [i – lowbit(i)+1,i]

很像完全二叉树呢

此图有以下性质

  • 同层节点表示区间不相交
  • 左儿子表示区间为父亲的子集
  • 右儿子表示区间和父亲不相交
  • 奇数的lowbit都为1

为什么树状数组能优化复杂度?

例如我们需要修改 x -> 修改包含x的所有区间

  • 从 x 点往上走
    • 若当前点是左儿子,则父亲节点包含x
    • 反之若当前点是右儿子,则父亲节点不包含x

修改时:寻找第一个大于 x 且是 x 祖先的数

显然这个条件不够 => 我们要寻找最小的大于 x 且 lowbit 大于 x 的数 相当于我们不会往右子树走

怎么快速让我们找到第一个lowbit比 x 大的数呢?

我们回到6=000110₂这个数来看,我们要让lowbit变大,那肯定只能加一个数(减一个数就变小了就不满足条件了不是?),我们要加的数还要尽可能小,那我们干脆直接加一个lowbit完事了!回到例子来看,就是加了一个lowbit(6)=2,刚好可以让这个数的lowbit变大!

修改完下一步是查询:我们寻找若干不相交的区间使其并集为[1,x]

  1. 首先取出以 x 结尾的区间 [x – lowbit(x) +1,x]
  2. 则只需要求[1,x – lowbit(x)]的区间 相当于在树上不断向左走

查询和修改刚好相反,是一个很优美的算法。一个是加lowbit一个是减去lowbit

时空复杂度说明:

  • 树高为O(log₂ n)
  • 修改和查询均只会使用每层最多一个点——每次加减一个lowbit
  • 故时间复杂度均为 O(log n)
  • 空间复杂度 O(n)——只用存每个节点的信息即可

常见用法

单点加法,查询区间和:

核心代码极其简单:

	void update(int x,int y){
		for(;x<=n;x+=(x&-x)) tree[x]+=y;//累加加上去
	}
	int Ans(int x){
		int s=0;//求答案 类似于前缀和
		for(;x;x-=(x&-x)) s+=tree[x];
		return s; 
	}

我们要在上面做一些变形

  • 将 tree[x] 修改为y——update(x,y-tree[x]);
  • 查询区间 [l,r]的和——Ans(r) – Ans(l-1);
  • 查询 x 点的值——Ans(x);

另一种变形是区间加减法,我们会用到“差分思想”

  • 区间加法,查询单点值
    inline int query(int x){
        int s=0;//用符号描述不易懂,这里采用代码描述
        for(;x>0;x-=x&-x) s+=tree[x];
        return s;
    }
  • 令区间 [l,r] 加上y——update(r+1,-y);update(l,y);
  • 查询 x 点的值——Ans(x)

树状数组到此基本就结束了,简单易懂(当然初次接触这个概念会有恐惧感,但整篇文章篇幅其实并不长呢!)

这里放一个拓展用法,拓展用法就不讲了咯!(过于麻烦了还是晚点学线段树吧)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇