树状数组(基础)

前言

在介绍树状数组之前,需要了解一个函数lowbit(),其是求一个数的二进制最低次幂的值,这个函数是自己手写的,不是编译器自带的。例如,5的二进制表示为101,那么lowbit(5)就是101的最后一位,即1;10的二进制表示为1010,那么lowbit(10)就是1010的最后两位,即10。

接下来介绍lowbit函数的实现原理。以0110(设为x)为例,将其取反再加一,结果为1010(由于在计算机系统中,负数是由补码表示的,而补码就是由正数的原码取反再加一得来,故1010等于-x),0110 & 1010 = 0010,其就是0110的二进制最低次幂的值。所以x & -x ,最终的结果就是该数的二进制最低次幂的值。

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

下面进入正题,正式开始介绍树状数组。树状数组常用来加速原始逻辑数组(逻辑上的数组,而非0 1逻辑数组,实际操作是对树状数组本身)的区间求和。树状数组维护的区间是[1,r],而非[0,r]

定义

  1. 第i个位置记录(i - lowbit(i), i]中的数字的和。这一点就是和普通数组最大的不同,普通数组第i个位置表示的就是第i个数。
  2. 第i个位置的父节点是i  + lowbit(i)

对定义的实例解释:第4个位置记录的是(4 - 4, 4],即[1,4]范围内数字的和,第6个位置记录的是[5, 6]范围数字的和;第5个位置的父节点为 5 + 1 = 6,即6是5的父节点。正是由于这样的结构,使得其名字为“树状数组”。

性质

  1. 对于某一个结点i,其所有子节点记录的区间不会相互覆盖,且按照从小到大的顺序依次覆盖(i - lowbit(i), i]。例如,对于结点8,其所有子节点为4,6,7,区间分别为[1,4]  [5,6]  [7,7]。
  2. 对于某一个结点i,仅会被i和它的祖先节点所覆盖。例如,结点2仅仅被结点2,4,8所覆盖。

查询操作

这一步也是和普通数组有着显著的区别。普通数组是直接暴力累加,树状数组是分步累加。

目的:查询树状数组的[1,r]范围内所有数字的和

原理:以结点7为例,其二进制表示为111,有三个1,故要累加三次,[1,7]的和被分解为[1,4]  [5,6]  [7,7]这三者的和。时间复杂度为log(n),因为n的二进制表示有log(n)个数字,而这些数字中最多有log(n)个1,故最坏情况下的复杂度为log(n)。

int query(int r) {  // 查询[1,r]范围内所有数字的和 
	int ans = 0;
	while (r != 0) {
		ans += d[r];
		r -= lowbit(r);
	}
	return ans;
} 

修改操作

目的:给数组x位置上的数加上y

原理:由性质2,对于某一个结点i,其仅会被i和它的祖先节点所覆盖,即i和它的祖先包含了i。

故要对它们全部都加上y。时间复杂度为log(n)。

void alter(int x, int y) {  // 在x位置上的数加上y 
	while (x <= n) {
		d[x] += y;
		x += lowbit(x);
	} 
} 

实例练习

洛谷P3374 树状数组

直接暴力,修改操作的复杂度为O(1),查询操作的复杂度为O(n),整体最坏的时间复杂度为O(n*m),会超时。使用树状数组这种数据结构,会使得修改和查询的复杂度得到均衡,使得整体的复杂度降到 log(n)。

#include<bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10; 
int d[N];
int n, m, num;

int lowbit(int x) {
	return x & -x;
}
 
int query(int r) {  // 查询[1,r]范围内所有数字的和 
	int ans = 0;
	while (r != 0) {
		ans += d[r];
		r -= lowbit(r);
	}
	return ans;
} 

void alter(int x, int y) {  // 在x位置上的数加上y 
	while (x <= n) {
		d[x] += y;
		x += lowbit(x);
	} 
} 

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> num;
		alter(i, num);
	}
	
	int a, b, c;
	for (int i = 1; i <= m; i++) {
		cin >> a >> b >> c;
		if (a == 1) {
			alter(b, c);
		} else if (a == 2) {
			cout << query(c) - query(b-1) << endl;
		}
	}
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值