• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

SinceBelieve

Ad infinitum.
  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【算法】树状数组

 

 

 

树状数组(Binary Indexed Tree / Fenwich Tree)

目录

  • 1 算法特性
  • 2 算法描述
    • 2.1 求和
    • 2.2 更新元素
    • 2.3 读取元素的值
    • 2.4 查找元素
    • 2.5 多维数状数组
  • 3 练习题

1 算法特性

  • 以下讨论中,数组下标从 1 开始而不是 0 ;以二进制表示整数时,LSB 在右侧,比特下标从 0 开始(例如一个字节表示为 \(b_7b_6 \ldots b_0\) ).
  • 对于一个元素序列 \(a_1, a_2, \ldots, a_n\) ,考虑以下两种操作:
    • 操作一:读写任意元素 \(a_k, k \in [1, n]\).
    • 操作二:计算元素序列之和 \(\displaystyle\sum_{i = j}^{k}a_i = a_j + a_{j+1} + \cdots + a_{k}\).
  • 如果使用元素数组 \(\mbox{Array}\) ,即 \(\mbox{Array}[i]\) 用于存储 \(a_i\) ,那么操作一的开销为 \(O(1)\) ,操作二的开销为 \(O(n)\).
  • 如果使用前 \(n\) 项和数组 \(\mbox{Sum}\) ,即 \(\mbox{Sum}[n]\) 用于存储 \(\displaystyle\sum_{i=1}^{n}a_i=a_1+a_2+\cdots+a_n\) ,那么操作一的开销为 \(O(n)\) ,操作二的开销为 \(O(1)\).
  • 考虑 \(m\) 次任意操作,最坏情况下两种数据结构的开销都是 \(O(m * n)\).
  • 今天讨论的树状数组 \(\mbox{Tree}\) 可以将最坏情况下 \(m\) 次任意操作的开销降低为 \(O(m\log\,n)\) ,即对于操作一和操作二,开销均为 \(O(\log\,n)\).

2 算法描述

 

2.1 求和

  • 对于数组下标 \(index\) ,现在考虑通过对应的数组元素 \(\mbox{Tree}[index]\) ,求出序列 \(\{a_n\}\) 的前 \(index\) 项的和,即求 \(\mbox{Sum}[index]\).
  • 我们考虑下标的二进制表示: \(index = 2^{e_1} + 2^{e_2} + \cdots + 2^{e_k}\) ,其中 \(e_1 > e_2 > \cdots > e_k\).
  • 我们可以将 \(\mbox{Sum}[index]\) 拆分成 \(k\) 个互不重叠的组,分别包含 \(2^{e_1}, 2^{e_2}, \ldots, 2^{e_k}\) 个元素,分别求和后再相加.
  • 例如, \(index = 13 = 8 + 4 + 1 = 2^3 + 2^2 + 2^0\) ,我们可以将 \(\mbox{Sum}[13]\) 拆分成三个组,分别含有 \(8=2^3,4=2^2,1=2^0\) 个元素.
  • 我们规定:\(\mbox{Tree}[index]\) 保存的是上述划分中,元素数目为 \(2^{e_k}\) 的那个组中所有元素的和.
  • 考虑到下标的这种特殊意义,那么通过 \(\mbox{Tree}[index]\) 求 \(\mbox{Sum}[index]\) 可以通过递归方式进行:
    1. 将计算结果 \(\mbox{sum}\) 初始化为零: \(\mbox{sum} \leftarrow 0\).
    2. 将 \(\mbox{Tree}[index]\) 加入到计算结果中: \(\mbox{sum} \leftarrow \mbox{sum} + \mbox{Tree}[index]\).
    3. 将 \(index\) 的二进制表示中从左到右最后一个 \(1\) 改为 \(0\) ,得到新的数组下标 \({index}' = 2^{e_1} + 2^{e_2} + \cdots + 2^{e_{k - 1}}\).
    4. 若 \({index}'\) 的值为零则算法终止,否则 \(index \leftarrow {index}'\) ,回到步骤 2.
  • 算法的执行过程中执行了若干次步骤 2,依次将 \(2^{e_k}, 2^{e_{k-1}},\ldots,2^{e_1}\) 个元素加入到计算结果中,最终计算结果将包含 \(index\) 个元素之和.
  • 显然对于读写元素操作和序列求和操作,开销均为 \(O(\log\,n)\).
  • 到这里问题已经解决了一半,另一半是,确保前 \(index\) 项分成的 \(k\) 个组互不重叠.
  • 按照之前的规定,能够通过 \(\mbox{Tree}[index]\) 求 \(\mbox{Sum}[index]\) ,并能够通过 \(\mbox{Tree}[{index}']\) 求 \(\mbox{Sum}[{index}']\)
  • 那么 \(\mbox{Sum}[index] = \mbox{Tree}[index] + \mbox{Sum}[{index}']\).
  • 那么 \(\mbox{Tree}[index] = \mbox{Sum}[index] - \mbox{Sum}[{index}']\).
  • 也就是说 \(\mbox{Tree}[index]\) 中存储的是下标范围在 \([index-2^r+1, index]\) 范围内的所有元素之和,其中 \(r\) 是 \(index\) 的二进制表示中从左到右最后一个 1 的位置.
  • 例如,一个包含 16 个元素的数状数组的具体情况如下:
    树状数组下标下标二进制表示元素下标范围图示
    1 0001 \([\phantom{0}1, \phantom{0}1] = [\phantom{0}1-2^0+1, \phantom{0}1]\) \(\qquad \underline{a_1} \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    2 0010 \([\phantom{0}1, \phantom{0}2] = [\phantom{0}2-2^1+1, \phantom{0}2]\) \(\qquad \underline{a_1 \quad a_2} \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    3 0011 \([\phantom{0}3, \phantom{0}3] = [\phantom{0}3-2^0+1, \phantom{0}3]\) \(\qquad a_1 \quad a_2 \quad \underline{a_3} \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    4 0100 \([\phantom{0}1, \phantom{0}4] = [\phantom{0}4-2^2+1, \phantom{0}4]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4} \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    5 0101 \([\phantom{0}5, \phantom{0}5] = [\phantom{0}5-2^0+1, \phantom{0}5]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad \underline{a_5} \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    6 0110 \([\phantom{0}5, \phantom{0}6] = [\phantom{0}6-2^1+1, \phantom{0}6]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad \underline{a_5 \quad a_6} \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    7 0111 \([\phantom{0}7, \phantom{0}7] = [\phantom{0}7-2^0+1, \phantom{0}7]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad \underline{a_7} \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    8 1000 \([\phantom{0}1, \phantom{0}8] = [\phantom{0}8-2^3+1, \phantom{0}8]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8} \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    9 1001 \([\phantom{0}9, \phantom{0}9] = [\phantom{0}9-2^0+1, \phantom{0}9]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad \underline{a_9} \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    10 1010 \([\phantom{0}9, 10] = [10-2^1+1, 10]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad \underline{a_9 \quad a_{10}} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    11 1011 \([11, 11] = [11-2^0+1, 11]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad \underline{a_{11}} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    12 1100 \([\phantom{0}9, 12] = [12-2^2+1, 12]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad \underline{a_9 \quad a_{10} \quad a_{11} \quad a_{12}} \quad a_{13} \quad a_{14} \quad a_{15}\)
    13 1101 \([13, 13] = [13-2^0+1, 13]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad \underline{a_{13}} \quad a_{14} \quad a_{15}\)
    14 1110 \([13, 14] = [14-2^1+1, 14]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad \underline{a_{13} \quad a_{14}} \quad a_{15}\)
    15 1111 \([15, 15] = [15-2^0+1, 15]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad \underline{a_{15}}\)
  • 例如,通过 \(\mbox{Tree}[6]\) 求 \(\mbox{Sum}[6]\) :
    树状数组下标下标二进制表示元素下标范围图示
    6 0110 \([5, 6] = [6-2^1+1, 6]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad \underline{a_5 \quad a_6} \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    4 0100 \([1, 4] = [4-2^2+1, 4]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4} \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
  • 例如,通过 \(\mbox{Tree}[13]\) 求 \(\mbox{Sum}[13]\) :
    树状数组下标下标二进制表示元素下标范围图示
    13 1101 \([13, 13] = [13-2^0+1, 13]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad \underline{a_{13}} \quad a_{14} \quad a_{15}\)
    12 1100 \([\phantom{0}9, 12] = [12-2^2+1, 12]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad \underline{a_9 \quad a_{10} \quad a_{11} \quad a_{12}} \quad a_{13} \quad a_{14} \quad a_{15}\)
    8 1000 \([\phantom{0}1, \phantom{0}8] = [\phantom{0}8-2^3+1, \phantom{0}8]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8} \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
  • 例如,通过 \(\mbox{Tree}[15]\) 求 \(\mbox{Sum}[15]\) :
    树状数组下标下标二进制表示元素下标范围图示
    15 1111 \([15, 15] = [15-2^0+1, 15]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad \underline{a_{15}}\)
    14 1110 \([13, 14] = [14-2^1+1, 14]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad \underline{a_{13} \quad a_{14}} \quad a_{15}\)
    12 1100 \([\phantom{0}9, 12] = [12-2^2+1, 12]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad \underline{a_9 \quad a_{10} \quad a_{11} \quad a_{12}} \quad a_{13} \quad a_{14} \quad a_{15}\)
    8 1000 \([\phantom{0}1, \phantom{0}8] = [\phantom{0}8-2^3+1, \phantom{0}8]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8} \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
  • 求和需要依次访问数组中的一些元素,将各个数组下标看作节点,将下标的变换看作边,那么将得到一个树形结构,这是“树状数组”名字的由来:

  • 还有一个问题,那就是怎样高效地从数组下标的二进制表示中去掉最后一个 1.
  • 若 \(index\) 非零,则其二进制表示可以写为 \(p1q\) ,其中 \(p\) 和 \(q\) 都为二进制串,并且 \(q\) 全部由 0 组成.
  • 对该二进制串取反得到 \(\bar{p}0\bar{q}\) ,由于 \(q\) 全部由 0 组成,所以 \(\bar{q}\) 全部由 1 组成.
  • 在 \(\bar{p}0\bar{q}\) 上加 1 得到 \(\bar{p}1q\) ,将该串与原串 \(p1q\) 进行按位与运算得到 \(\bar{p}1q \wedge p1q = p'1q\) ,其中 \(p'\) 全部由 0 组成并且与 \(p\) 位数相同.
  • \(p'1q\) 中只包含一个 1,并且就是 \(index\) 的二进制表示中最后一个 1 ,现在可以通过减法将其去掉.
  • 取反然后加一,这个操作其实就是求补码,所以通过 \(\mbox{Tree}[index]\) 求 \(\mbox{Sum}[index]\) 的过程的代码片段可以这样写:
    int sum(int tree [], int index)
    {
        int result = 0;
        while ( index > 0 )
        {
            result += tree[index];
            index -= ( index & -index );
        }
        return result;
    }
    

2.2 更新元素

  • 前面说过 \(\mbox{Tree}[index]\) 存储的是若干个元素的和,当更新序列元素的时候,所有求和范围包含该序列元素的数组元素都必须更新.
  • 还是以前面包含 15 个元素的树状数组作为例子,当要更新 \(a_1\) 时,需要更新的数组元素包括:
    树状数组下标下标二进制表示元素下标范围图示
    1 0001 \([1, 1] = [1-2^0+1, 1]\) \(\qquad \underline{a_1} \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    2 0010 \([1, 2] = [2-2^1+1, 2]\) \(\qquad \underline{a_1 \quad a_2} \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    4 0100 \([1, 4] = [4-2^2+1, 4]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4} \quad a_5 \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    8 1000 \([1, 8] = [8-2^3+1, 8]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8} \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
  • 当要更新 \(a_5\) 时,需要更新的数组元素包括:
    树状数组下标下标二进制表示元素下标范围图示
    5 0101 \([5, 5] = [5-2^0+1, 5]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad \underline{a_5} \quad a_6 \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    6 0110 \([5, 6] = [6-2^1+1, 6]\) \(\qquad a_1 \quad a_2 \quad a_3 \quad a_4 \quad \underline{a_5 \quad a_6} \quad a_7 \quad a_8 \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
    8 1000 \([1, 8] = [8-2^3+1, 8]\) \(\qquad \underline{a_1 \quad a_2 \quad a_3 \quad a_4 \quad a_5 \quad a_6 \quad a_7 \quad a_8} \quad a_9 \quad a_{10} \quad a_{11} \quad a_{12} \quad a_{13} \quad a_{14} \quad a_{15}\)
  • 我们发现需要更新的数组元素下标具有规律性:相邻两次更新中数组下标之差为 2 的幂,换句话说这个差值的二进制表示中只含有一个 1,并且其位置和较小的那个数组下标中最后一个 1 的位置相同.
  • 这启发我们用类似之前求和的方法来更新元素,代码片段可以这样写:
    void update(int tree [], int size, int index, int delta)
    {
        while ( index <= size )
        {
            tree[index] += delta;
            index += ( index & -index );
        }
    }
    

2.3 读取元素的值

  • 当空间足够的时候,可以额外使用一个数组记录各个元素的值,这样读取元素取值的开销为 \(O(1)\).
  • 当然我们也可以直接通过树状数组求指定下标序列元素的值.
  • 一个很容易想到的策略是,分别求出前 \(i, i - 1\) 项之和,再做减法得到下标为 \(i\) 的序列元素: \(a_i = \mbox{Sum}[i] - \mbox{Sum}[i - 1]\).
  • 回忆之前对“树状数组”名字的解释,每次求和都是沿着树中某条路径向上访问直到树根,那么两次求和所对应的两条路径重合的部分我们可以不必进行求和.
  • 进一步想,两条路径在什么情况下会重合,为了回答这个问题,我们回到下标的二进制表示上来.
  • 假设下标 \(index\) 的二进制表示为 \(p1q\) ,其中 \(p, q\) 都是二进制串,并且 \(q\) 全部由 0 组成.
  • 那么 \(index - 1\) 的二进制表示为 \(p0\bar{q}\) ,其中 \(\bar{q}\) 全部由 1 组成.
  • 由于下标的二进制表示和路径上的点时一一对应的,下标通过将最后一个 1 变成 0 发生变化,那么两个二进制表示的最长公共前缀就对应了公共路径.
  • 上面两个二进制表示的公共部分是 \(p1q \wedge p0\bar{q} = p0q\) ,其实“与运算”不能求任意两个二进制串的最长公共前缀,但对于 \(index\) 和 \(index - 1\) 这是合适的.
  • \(p0q\) 还可以通过 \(p1q\) 去掉最后一个 1 得到,回忆之前的迭代式 \(\mbox{Sum}[index] = \mbox{Tree}[index] + \mbox{Sum}[{index}']\) ,其中的 \(index'\) 的二进制表示就是 \(p0q\).
  • 换句话说,在求 \(\mbox{Sum}[index]\) 和 \(\mbox{Sum}[index - 1]\) 的过程中都会通过迭代进入求 \(\mbox{Sum}[index']\) 的过程,这两个重复的求和过程我们可以设法省略.
  • 再换句话说,由于 \(\mbox{Tree}\) 中的元素对应一组序列元素之和,求下标为 \(index\) 的序列元素其实就是从 \(\mbox{Tree}[index]\) 中减去所有下标不是 \(index\) 的元素.
  • 读取序列元素值的代码片段可以这样写:
    int read(int tree [], int index)
    {
        int idx = index - ( index & -index );
        int sum = tree[index];
        index--;
        while ( index != idx )
        {
            sum -= tree[index];
            index -= ( index & -index );
        }
        return sum;
    }
    
  • 话说上面的代码中 idx 还可以通过 index & ( index - 1 ) 得到.
  • 这种改进不能降低时间复杂度的阶,例如当要访问的元素下标的二进制表示中之含有一个 1 的时候.

2.4 查找元素

  • 有时候我们会有这样的需求,找到某个元素,使其前 \(n\) 项和为给定的值.
  • 利用 \(\mbox{Sum}\) 数组和二分算法,单次查找的开销为 \(O(\log\,n)\) ,但更新数据的开销为 \(O(n)\) ,所以只适合静态数据.
  • 利用 \(\mbox{Tree}\) 数组,可以使得查找和更新数据的开销都为 \(O(\log\,n)\).
  • 我们用定长二进制串表示下标,二进制串的长度由最大下标决定.
  • 然后我们从高到低依次确定二进制串的每一位,由于前 \(n\) 项和跟 \(n\) 是正相关的,因此按照这个顺序可以正确逼近目标下标.
  • 注意,如果序列元素中存在负值,那么前面提到的所有与二分法有关的方法都不适用,因为前 \(n\) 项和不再是单调的了.
  • 代码片段:
    int tree[MAX_VALUE + 1];
    
    int find(int tree [], int sum)
    {
        int index = 0;
        int bit_mask = BIT_MASK; // The constant BIT_MASK is the highest bit of MAX_VALUE.
        while ( bit_mask != 0 )
        {
            int new_index = index + bitmask;
            if ( ( new_index <= MAX_SIZE ) && ( sum >= tree[new_index] ) )
            {
                index = new_index;
                sum -= tree[new_index];
            }
            bit_mask >>= 1;
        }
        return ( bitMask == 0 ? index : -1 );
    }
    

2.5 多维数状数组

  • 数状数组可以很容易地扩展到多维.
  • 例如,更新二维树状数组的元素:
    int tree[MAX_X + 1][MAX_Y + 1];
    
    void update(int x, int y, int delta)
    {
        int i, j;
        for ( i = x; i <= MAX_X; i += ( i & -i ) )
        {
            for ( j = y; j <= MAX_Y; j += ( j & -j ) )
            {
                tree[i][j] += delta;
            }
        }
    }
    
  • 例如,求矩形区域 \((1, 1), (x, y)\) 内所有元素之和:
    int tree[MAX_X][MAX_Y];
    
    int sum(int x, int y)
    {
        int result = 0;
        int i, j;
        for ( i = x; i > 0; i -= ( i & -i ) )
        {
            for ( j = y; j > 0; j -= ( j & -j ) )
            {
                result += tree[i][j];
            }
        }
        return result;
    }
    
  • 由于每一维的空间可能很大,所以通常多维树状数组在空间上不可行.

3 练习题

题目难度普遍中等偏难,动态数组不是唯一解法,但通常是较好的解法(时间复杂度的意义上);另外一个特点是,数学模型不是很直观,需要自己构造.

  • POJ 3321 (偏难)
  • POJ 3468 (偏难)
  • POJ 2352 (中等)
  • POJ 3067 (中等)
  • POJ 2155 (中等)
  • POJ 2299 (中等)
  • POJ 3928 (中等)
  • POJ 1195 (简单)
  • POJ 2029 (简单,暴力可过)
  • POJ 2182 (简单,暴力可过)

Date: 2013-06-17T23:36+0800

Author: xiaoyao

Org version 7.9.2 with Emacs version 24

posted on 2013-06-27 00:08  SinceBelieve  阅读(258)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3