树状数组简单用法

概念
给出一个长度为n的数组,完成一下两种操作:
1、在第i个位置加上k。
2、查询区间[i,j]内每个数的和。
朴素算法
1、单点修改,O(1)。
2、区间查询,O(n)。
树状数组
1、单点修改,O(logn)。
2、区间查询,O(logn)。
这里就可以发现:如果题上的n特别大的话朴素算法时间复杂度太高,然而用树状数组就可以用Olog(n)的时间来完成这些操作。所以说,树状数组最大的好处就是快速的改变某一个点的值,快速的查询某一段区间的和。
算法前提
lowbit运算:正整数x的二进制最低位的1及后面的0构成的数值
例:12的二进制[1100] , lowbit(12)=lowbit([1100])=[100]=4。
函数实现:

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

主要思想
树状数组最主要的思想就是一颗特殊的“树”来维护“前缀和”,从而把时间降到log(n)。
对于一个序列,可以建成这样的一棵树(这里以n=8为例)
在这里插入图片描述
1、每个结点t[x]保存以x为根的子树中叶结点值的和
2、每个结点覆盖的长度为lowbit(x)
3、t[x]结点的父结点为t[x + lowbit(x)]
4、树的深度为log2n+1
其实这里t[i]可以理解为父节点管理着它的子节点和它自己,并且t[i]的值表示他管理的所有的子节点和他的本身值。
例: t[8]管理着t[4]、t[6]、t[7]、t[8]。()
t[7]管理着t[7]。
t[6]管理着t[5]、t[6]。
t[5]管理着t[5]。
t[4]管理着t[2]、t[3]、t[4]。
t[3]管理着t[3]。
t[2]管理着t[2]、t[1]。
t[1]管理着t[1]。
这些在上图里面都有体现的
其实看到这里,可能还是一头雾水的,下面介绍了两种操作,应该就会懂这些都是什么意思
树状数组的两个操作
1、修改元素:add(x,k)表示在原数组第x个位置加上k。
这里以add(3,5)为例、即:
在这里插入图片描述
在3的位置上加上5,要一层一层的去找到所有的父节点,然后所有的父节点全都加上5,即t[3]、t[4]、t[8]都要加上5,那么问题来了,怎样去一层一层的找到父节点呢?
这里就要用到上面提到的lowbit操作了,以3为例,3+lowbit(3)=4,4+lowbit(4)=8.
这样的lowbit操作 不仅对3适合,对所有数都是适合的.这个在这里就不去证明了

void add(int x, int k)
{
    for(int i = x; i <= n; i += lowbit(i))
        t[i] += k;
}

2、查询操作
ask(x)表示查询前x个数的和
这里以ask(7)为例
在这里插入图片描述
如果要查询前7个元素的和 那么就可以是t[7]+t[6]+t[4],这样就可以完成前期各元素的和了,那么问题来了 如何找到6和4呢,这里有要用到lowbit操作了
即:7-lowbit[7]=6 6-lowbit[6]=4;
即:

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

这样就可以完成在log(n)的时间复杂度进行这两种操作

其实这么多的引入也没什么用,主要就是这三个函数

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

void add(int x,int c){//在x的地方加上c
	 for(int i=x;i<=n;i+=lowbit(i)) t[i]+=c;
}

int sum(int x){//查询前x个数的和,即x的前缀和
	int res=0;
	for(int i=x;i;i-=lowbit(i)) res+=t[i];
	return res;
}

可以看一下洛谷上面的这个题
洛谷模板题
2021河南省赛题,树状数组题

### C++ 中树状数组(Binary Indexed Tree)的实现与用法 #### 树状数组简介 树状数组(Fenwick Tree),也称为二叉索引树,是一种高效的数据结构,特别适用于处理动态单点更新和前缀区间查询的问题。这种数据结构能够快速完成数和检索操作,在许多算法竞赛和实际应用中非常有用。 #### 基本概念 树状数组的核心在于其节点存储的是原数组某一段区间的累积,而不是单一位置上的具体。通过巧妙利用低比特位的操作来构建这些关系,可以使得每次查询或修改的时间复杂度都保持在 O(log n)[^2]。 #### 主要功能 树状数组支持两种基本操作: - **Update(index, value)**: 更新指定下标的元素相应调整受影响的所有父节点。 - **Query(prefix_length)** 或者 `sum(range_end)` : 计算给定长度范围内所有数之和。 #### 实现细节 下面展示了一个简单树状数组类定义及其核心方法`update()` 和 `query()` 的实现方式: ```cpp class BITree { private: vector<int> bit; public: explicit BITree(int size):bit(size+1){} void Update(int index, int delta){ while (index < static_cast<int>(bit.size())) { bit[index] += delta; index += index & (-index); } } int Query(int prefixLength)const{ int result = 0; while (prefixLength > 0) { result += bit[prefixLength]; prefixLength -= prefixLength & (-prefixLength); } return result; } }; ``` 上述代码片段展示了如何创建一个具有特定大小的树状数组实例以及执行更新和查询的方法[^1]。 对于更复杂的场景,比如需要维护多个维度的信息或者计算差分等情况下,则可能涉及到更高级的应用形式,如双树状数组组合使用以满足不同类型的查询需求[^3]。 #### 使用示例 这里给出了一段完整的程序用来演示如何初始化操作一个简单的一维树状数组对象: ```cpp #include <iostream> using namespace std; // ... 上面提到过的BITree 类定义 ... int main(){ const int SIZE=9; int data[] = {1,2,3,4,5,6,7,8,9}; // 初始化树状数组 BITree tree(SIZE); // 向树状数组初始 for(int i=0;i<SIZE;++i){ tree.Update(i+1,data[i]); } cout << "Sum of first four elements is:"<<tree.Query(4)<< endl; return 0; } ``` 这段代码首先建立了一个容量为9的树状数组,接着依次向其中入了一些整数作为起始状态;最后打印出了最前面四个数字相之总和的结果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值