树状数组算法

树状数组,是一种快速解决单点更新区间查询(或区间更新单点查询)的数据结构.
a1,a2,a3,...,an a 1 , a 2 , a 3 , . . . , a n 为长度为n( 105) ≤ 10 5 ) 的数列. 现在有以下两种类型的操作m( 104 ≤ 10 4 )个:

  1. ADD A B (修改数列中A位置上的元素值增加B)
  2. QUERY A B (询问区间[A, B]的元素值和)

直接求解: O(1)的更新,m次O(n)的查询复杂度. O(mn) 不能接受.

引入辅助数组C. 其中:
C1=a1 C 1 = a 1
C2=a1+a2 C 2 = a 1 + a 2
C3=a3 C 3 = a 3
C4=a1+a2+a3+a4 C 4 = a 1 + a 2 + a 3 + a 4
C5=a5 C 5 = a 5
C6=a5+a6 C 6 = a 5 + a 6
C7=a7 C 7 = a 7
C8=a1+a2+a3+a4+a5+a6+a7+a8 C 8 = a 1 + a 2 + a 3 + a 4 + a 5 + a 6 + a 7 + a 8
... . . .
C16=a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13+a14+a15+a16 C 16 = a 1 + a 2 + a 3 + a 4 + a 5 + a 6 + a 7 + a 8 + a 9 + a 10 + a 11 + a 12 + a 13 + a 14 + a 15 + a 16

n,Cn=an2k+1+an2k+2+...+an=ni=n2k+1ai,kn0 对 于 任 意 的 n , C n = a n − 2 k + 1 + a n − 2 k + 2 + . . . + a n = ∑ i = n − 2 k + 1 n a i , 其 中 k 为 n 二 进 制 表 示 中 末 尾 0 的 个 数

对于数组C可以构造一棵树, 如下图所示(图片摘自百度百科):

图片摘自百度百科

上图中由C数组构造的一棵树满足: CiCj,j+2k==i,CiCj(kj0) 对 于 节 点 C i 和 C j , 如 果 j + 2 k == i , 则 C i 是 C j 的 父 节 点 ( 其 中 k 为 j 的 末 尾 0 个 数 )
lowbit(x)=2k(kx0). l o w b i t ( x ) = 2 k ( k 为 x 二 进 制 表 示 末 尾 0 的 个 数 ) .

  1. 求区间[A, B]元素和
    定义 sum[i]=a1+a2+...+ai s u m [ i ] = a 1 + a 2 + . . . + a i , 问题转化成求sum[B] - sum[A - 1]. 推倒sum[i]的求法:

    sum[i]=a1+a2+...+ai=a1+a2+...+ai2k+ai2k+1+...+ai=a1+a2+...+ai2k+Ci,(2k==lowbit(i))=a1+a2+...+ailowbit(i)+Ci(1)(2)(3)(4) (1) s u m [ i ] = a 1 + a 2 + . . . + a i (2) = a 1 + a 2 + . . . + a i − 2 k + a i − 2 k + 1 + . . . + a i (3) = a 1 + a 2 + . . . + a i − 2 k + C i , ( 2 k == l o w b i t ( i ) ) (4) = a 1 + a 2 + . . . + a i − l o w b i t ( i ) + C i

    因而计算 sum[i] s u m [ i ] 的过程如下代码所示:

    int lowbit(int x){
        return x & (-x);
    }
    
    int sum(int n){
        int ans = 0;
        while(n > 0){
            ans += c[n];
            n -= lowbit(n);
        }
        return ans;
    }

    计算sum[i]的过程实则为去除i末尾1的过程, 因而算法复杂度为O(logn)

  2. 单点更新位置i上的元素增加d.
    当更新 ai a i 时,从树的性质能看出,只需要更新 Ci C i 及其所有的父节点 Ci+lowbit(i)... C i + l o w b i t ( i ) . . . 等

    void add(int i, int d){
        while(i < MAX){
            c[i] += d;
            i += lowbit(i);
        }
    }
    

    由于树的高度最多为log(n). 因而更新维护C数组的复杂度也是O(logn).

树状数组解决的问题举例:

  1. 单点更新,区间查询.
    最原始的算法套用. 查询[A, B]区间和, ans = sum[B] - sum[A - 1]

  2. 区间更新,单点查询:
    【例】
    a) ADD A B d (将区间[A, B]中的元素增加d)
    b) QUERY x (查询x的值)
    这里只需把a)操作转化为add(A, d), add(B + 1, -d). 即可.

  3. 二维树状数组:
    【例】 二维矩阵M*N(M, N <=10^3), 有以下两种操作
    a) ADD x y d(将x,y 位置的元素增加d)
    b) QUERY x1 y1 x2 y2( 查询矩形区域(x1,y1)(左上角), (x2, y2)(右下角)的和)
    计算ans = sum(x2, y2) - sum(x2, y1 - 1) - sum(x1 - 1, y2) + sum(x1 - 1, y1 - 1)
    二维树状数组:

    void add(int x, int y, int d){
        for(int i = x; i <= n; i += lowbit(i)){
            for(int j = y; j <= n; j += lowbit(j)){
                c[i][j] += d;
            }
        }
    }
    
    int sum(int x, int y){
        int ans = 0;
        for(int i = x; i > 0; i -= lowbit(i)){
            for(int j = y; j > 0; j -= lowbit(j)){
                ans += c[i][j];
            }
        }
        return ans;
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值