树状数组(自用)

 一.使用场景

1.前缀和查询

2.单点更新

3.区间查询和更新

4.逆序对计数

5.数组频率计数

二.lowbit()运算

在使用树状数组的时候我们先要学习,lowbit()运算。即非负整数n在二进制表示下最低为1及其后面的0构成的数值。

考虑一个正整数 x 的二进制表示形式,例如 x = 12,其二进制表示为 1100。我们观察到,x 的二进制表示中最低位的 1 所对应的值是 4(即 x & (-x))。

lowbit 的原理就是利用了这个性质,通过按位与运算(&)来获取二进制数中最低位的 1 所代表的数值。具体而言,对于任意一个正整数 x,其 lowbit 值就是 x & (-x)。

举例来说,对于 x = 12,其二进制表示为 1100,那么 x & (-x) = 4。同样地,对于 x = 10,其二进制表示为 1010,那么 x & (-x) = 2。

lowbit 的作用在于帮助确定树状数组中每个索引位置所代表的区间大小。在树状数组中,每个索引位置 i 的二进制表示中,最低位的 1 所对应的区间大小就是 i & (-i)。代码如下:

//非负整数n在二进制表示下最低为1及其后面的0构成的数值
int lowbit(int i)
{
	return i & -i;
}

例如,索引 6 的二进制表示为 110(从右到左按位编号),那么对应区间的大小为 2,即包括原始数组中的第 6、5 两个元素。类似地,索引 10 的二进制表示为 1010,对应区间的大小为 2,即包括原始数组中的第 10、9 两个元素。

三.原理

如果要计算a1到a8的和,我们需要从a1加到a8,这样太过麻烦。我们可以两两相加,如a1+a2=a9。以此类推,最后计算得到总和a15 = a14 +a13。

现在假设有一个树状数组 sums_,其中 sums_[i] 表示原始数组中前 i 个元素的前缀和。那么 sums_[i] 可以被分割成若干个区间的和,每个区间的长度都是某个 2 的幂次(例如 1、2、4、8...)。

其中 sums_[i] 的二进制表示中最低位的 1 对应一个区间的和,而剩下的部分可以用来表示包含第 i 个元素的若干区间的和。这样,我们可以通过修改和查询树状数组的特定索引位置的值,实现单点修改和区间查询操作。

因此我们可以构建一个森林结构(图不太好看)

t[x]保存以x为根的子树中叶节点值的和

将每个t[x]的x转化为二进制后,我们发现每一层末尾的0的个数相同,0的个数与其覆盖的长度有关。我们还能发现t[x]节点的父节点为t[x + lowbit(x)]

我们可以得到:

t[x] = \sum_{i = x - lowbit(x) + 1}^{x} a[i]

三.实现操作

1.单点修改

先上代码:

// 单点修改,将第 i 个元素加上 delta
void update(int i, int delta) 
{
	while (i < n)
	{
		arr[i] += delta;
		i += lowbit(i);
	}
}

我们在进行单点修改的时候,我们需要在整棵树上维护这个值,需要一层层向上找到父节点(运用上述公式)

例如,如果我要对数组中的a[5]+delta,即我要对t[5]及其父节点都加上delta。则t[5],t[6],t[8]都要加5。

2.前缀和

先上代码:

// 查询区间 [1, i] 的和
int query(int i) 
{
	int sum = 0;

	while (i > 0) 
	{
		sum += arr[i];
		i -= lowbit(i);
	}
	return sum;
}

查询这个点的前缀和,需要从这个点向左上找到上一个节点,回到上一个节点的操作即为: i - lowbit(i),i为这个节点的下标。例如我要求sum(7),按照我们上面画的森林图,我需要加上t[7],t[6],t[4]。

四.总代码

#include<iostream>
using namespace std;
#define n  5//定义n为5

//初始化数组为n
int arr[n];

//非负整数n在二进制表示下最低为1及其后面的0构成的数值
int lowbit(int i)
{
	return i & -i;
}



// 单点修改,将第 i 个元素加上 delta
void update(int i, int delta) 
{
	while (i < n)
	{
		arr[i] += delta;
		i += lowbit(i);
	}
}

// 查询区间 [1, i] 的和
int query(int i) 
{
	int sum = 0;

	while (i > 0) 
	{
		sum += arr[i];
		i -= lowbit(i);
	}
	return sum;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值