树状数组入门介绍


树状数组

1.基本介绍

树状数组,也称作“二叉索引树”(Binary Indexed Tree)或 Fenwick 树。 它可以高效地实现如下两个操作:

1、单点更新。

2、数组前缀和的查询。

图片转载:地址

在这里插入图片描述

注:

​ 数组下标从1开始

​ 上面的二进制表示数组的下标

2.树状数组模板

lowbit(x)函数

l o w b i t ( x ) 函 数 : 返 回 x 的 最 后 一 位 1 , 如 x = 20 , 它 的 二 进 制 表 示 为 ( 10100 ) 2 则 l o w b i t ( 20 ) = ( 100 ) 2 = 4 lowbit(x)函数:返回x的最后一位1,如x = 20,它的二进制表示为(10100)_2则lowbit(20) = (100)_2 = 4 lowbit(x)x1x=20(10100)2lowbit(20)=(100)2=4

int lowbit(int x)
{
	return x & -x;
}

add(idx,v)更新函数

add(idx,v):给原数组idx位置上的数加上v,那么也要更新它的管理者(父节点),也给父节点加v,这样才能保证前缀和不变。

idx位置上它的父节点为:tr[idx + lowbit(idx)]
i d x + l o w b i t ( i d x ) 就 是 一 个 进 位 ( 从 最 后 一 位 1 开 始 ) 的 操 作 。 如 t r [ 12 ] , ( 1100 ) 2 在 最 后 一 位 1 进 位 , 即 110 0 2 ( 12 ) + 10 0 2 ( 4 ) = ( 10000 ) 2 = 16 , t r [ 16 ] 就 是 t r [ 12 ] 的 父 节 点 。 idx + lowbit(idx)就是一个进位(从最后一位1开始)的操作。如tr[12],(1100)_2在最后一位1进位,即1100_2(12) + 100_2(4) = (10000)_2 = 16,tr[16]就是tr[12]的父节点。 idx+lowbit(idx)1tr[12],(1100)2111002(12)+1002(4)=(10000)2=16,tr[16]tr[12]
那么更新操作为:tr[idx + lowbit(idx)] + v

void add(int idx, int v)
{
	for(int i = idx; i <= n; i += lowbit(i)) tr[i] += v;
}

query(x)查询函数

query(x):返回数组中位置1~x的前缀和

int query(int x)
{
	int sum = 0;
	for(int i = x; i; i -= lowbit(i)) sum += tr[i];
	return sum;
}

这个操作实质是将x拆分为它可含的二进制然后通过tr[]数组进行求和的,我们知道tr[]数组表示某一段数的和,在前面我们已经构造出了!。

如求位置在1~13数的和
13 的 二 进 制 为 ( 1101 ) 2 , 可 划 为 ( 1101 ) 2 = 13 、 ( 1100 ) 2 = 12 、 ( 1000 ) 2 = 8 13的二进制为(1101)_2,可划为(1101)_2 = 13、(1100)_2 = 12、(1000)_2 = 8 13(1101)2(1101)2=13(1100)2=12(1000)2=8
这个操作过程就是去掉最后一位1的实现的,当最终只剩下一个1的时,就不能在去掉了,至此操作结束。我们知道tr[]数组表示某一段的和,在基本介绍图中已介绍tr[x] = 区间[x - lowbit(x) + 1, x]的和,我们惊奇的发现:
t r [ 100 0 2 ] = t r [ 8 ] = 区 间 [ 1 , 8 ] 和 t r [ 110 0 2 ] = t r [ 12 ] = 区 间 [ 9 , 12 ] 和 t r [ 110 1 2 ] = t r [ 13 ] = 区 间 [ 13 , 13 ] 和 tr[1000_2] = tr[8] = 区间[1,8]和 \\ tr[1100_2] = tr[12] = 区间[9,12]和 \\ tr[1101_2] = tr[13] = 区间[13,13]和 tr[10002]=tr[8]=[1,8]tr[11002]=tr[12]=[9,12]tr[11012]=tr[13]=[13,13]
那么区间[1,13]的和 = tr[8] + tr[12] + tr[13]了!!!是不是很神奇呀!!!

3.模板例题

动态求连续区间和

【题目链接】1264. 动态求连续区间和 - AcWing题库

给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b] 的连续和。

输入格式
第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。

第二行包含 n 个整数,表示完整数列。

接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;k=1,表示第 a 个数加 b)。

数列从 1 开始计数。

输出格式
输出若干行数字,表示 k=0 时,对应的子数列 [a,b] 的连续和。

数据范围
1≤n≤100000,
1≤m≤100000,
1≤a≤b≤n,
数据保证在任何时候,数列中所有元素之和均在 int 范围内。

输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8
输出样例:
11
30
35

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;
int q[N];
int tr[N];

int n, m;
int lowbit(int x)  // 返回末尾的1和连续的0
{
   return x & -x;
}
// 给idx位置上的数加v,那么它的父节点(管理者也要加上v,保证前缀和不变)
void add(int idx, int v)
{
    for(int i = idx; i <= n; i += lowbit(i)) tr[i] += v;
}
// 求前缀和(1~x):对x的二进制进行拆分
int query(int x)
{
    int sum = 0;
    for(int i = x; i ; i -= lowbit(i)) sum += tr[i];
    return sum;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> q[i];
    for (int i = 1; i <= n; i ++ ) add(i, q[i]);// 预处理
    
    while (m -- )
    {
        int k, a, b;
        cin >> k >> a >> b;
        if(k == 0) cout << query(b) - query(a - 1) << endl;// 前缀和求区间和
        else add(a, b);
    }
    return 0;
}

总结

数组数组的原理理解起来个人感觉非常的绕QAQ,不管怎么样,总之模板先熟背!!!!

参考文献:

acwing蓝桥杯辅导课

哔哩哔哩:地址

哔哩哔哩:地址

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值