树状数组知识点整理

目录

0.前言

1.lowbit

求lowbit的原理解释:

2.树状数组的向上推导

 向上推导的原理?

3.树状数组的向下推导

向下推导的原理?

4.树状数组求区间和


0.前言

 先简单介绍一下树状数组:

树状数组,是一种修改和查询都为O_{log n}时间复杂度的数据结构,常用于数组的多次修改与访问。

树状数组就是由完全二叉树减去一部分节点形成的,类似于前缀和,但是其思想又超越了前缀和,故此具有更好的时间复杂度与更高的难度。

 (加点官方资料提升档次)

记住,待会儿要讲!

 树状数组的结构示意图

1.lowbit

首先,我们要讲一讲lowbit这东西。lowbit,翻译成人话就是最后的比特。何为最后的比特?就像这样——

   \left ( 10 \right )_{10}  =  \left(1010 \right )_{2},lowbit\left ( \left ( 10 \right )_{10} \right )=\left ( 10 \right )_{2}

    \left ( 15 \right )_{10}=\left ( 1111 \right )_{2},lowbit\left ( \left ( 15 \right )_{10} \right )=\left ( 1 \right )_{2}

lowbit,就是一个数字2进制中从右往左数第一个1及其右边的0所构成的二进制数字。

那lowbit怎么求呢?(还能怎么求,用for循环一位一位看呗)

                                                  Wrong Answer

正确操作:x&(-x)

(?)

(小朋友,你是否有很多问号?)

求lowbit的原理解释:

首先明确一点:当一个负数参加与运算时,会将其转换为补码

当 -x 转换为补码时,末尾全部的0变成1,最后的1变成0。再加1之后末尾所有的1(原来的0)都进位为0,直到碰见一个0(原来的1),就不会再进位,该位变成1。所以最低位到最右边的1的部分都不会变。而在其之左的部分因为取反,进行与运算后都会变成0。这就是lowbit的原理。

 那lowbit有什么用

呢?

不要着急,慢慢来看——

2.树状数组的向上推导

树状数组的向上推导,常用于单点修改。每次修改的时间复杂度为O\left ( logn \right )。为何?看图便知——

 以101(二进制)举例,每次向上推导时都会提高一个“层次”,且每个“层次”都只会访问一个节点。仔细观察不难发现,自下而上数第一层节点个数为四,第二层节点数为二,第三层节点数为一——随着层次增高,每层的节点个数以指数数量减少。因此,每一次向上推导都是O\left ( logn \right )级别的。 

 向上推导的原理?

那每次具体加多少呢?聪明的你想必已经猜到——lowbit。那为什么是lowbit呢?

(先思考,再看下文)

使用数学归纳法就可以发现每次加上lowbit是正确的。当然,我们也可以推理出结论。众所周知,二进制逢二进一。还是以101举例,101其实就是1*2^{2}+0*2^{1}+1*2^{0}。而树状数组和其有异曲同工之妙——每两个相同长度的部分数组,都会合并成一个更大、层数更高的部分数组。(实际中还要看位置关系)(想一想为什么这两个部分数组高度一定相同)于是,向上推导的过程就变成了一个把(n-i)拆分成若干个2的自然数次方的和的形式。为什么这个拆分会和lowbit有关呢?想一想,在某一位上缺1,那么这一位上一定就是1。既然我们是每次从小到大增加2的自然数次方,那么增加的自然就是lowbit。同时,这也从另一种角度解释了为什么向上推导的时间复杂度为O\left ( logn \right )——在极端情况下,(指原数约等于1)从1加到2^{n}要加n次,对于2^{n}来说就是log级别。 

void ad(int x, int k) {
	while (x <= n) {
		tree[x] += k;
		x += lowbit(x);
	}
}

3.树状数组的向下推导

其实向上推导和向下推导是相通的。向上推导主要应用于单点修改,而向下推导则主要应用于求[1,x]的区间和(哎,终于有点正常数组的样子了)只要理解了向上推导,就一定能理解向下推导。 废话少说,先放图——

 

向下推导的原理?

如图,从1000出发,那么[1,1000]的区间和就是tree_{100}+tree_{110}+tree_{111}+a_{1000}。同样的,我们把1000前面的大小为七的原数组拆分成2的自然数次方的形式:7=2^{2}+2^{1}+2^{0}。于是,通过同样的操作,我们一步步减去lowbi,一步步求和,直到减为0为止,求和也就用O\left ( logn \right )的时间复杂度给解决了。

4.树状数组求区间和

在前言中已经介绍过,树状数组常用于数组的多次修改与访问,且类似于前缀和(本人观点)。那树状数组是如何求区间和的呢?下面让我们来一探究竟。

回忆一下前缀和求区间值。比如[l,r]=sum[r]-sum[l-1]。那树状数组是不是一样的呢?答案是大同小异。运用我们刚刚学会(各位dalao给个面子配合一下)的向下推导,可以得到如下伪代码——

int getsum(int x) {
	int sum = 0;
	while (x) {
		sum += tree[x];
		x -= lowbit(x);
	}
	return sum;
}

  关于求特定区间,使用两次查询再作差即可,时间复杂度依然是O_{log n}

下面就是完整代码:

#include<iostream>
#define int short
#define lowbit(x) x&(-x)
using namespace std;
int n, m; //数组长度  操作次数
int tree[100005];
void ad(int x, int k) {
	while (x <= n) {
		tree[x] += k;
		x += lowbit(x);
	}
}
int getsum(int x) {
	int sum = 0;
	while (x) {
		sum += tree[x];
		x -= lowbit(x);
	}
	return sum;
}

signed main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		int x;
		cin >> x;
		ad(i, x);
	}
	while (m--) {
		int flag;
		cin >> flag;
		if (flag == 0) {//操作0:把a[x]增加k
			int x, k;
			cin >> x >> k;
			ad(x, k);
		} else {//操作1:求出区间[l,r]的和
			int l, r;
			cin >> l >> r;
			cout << getsum(r) - getsum(l - 1) << endl;
		}
	}
}
//代码动了点小手脚,不要无脑抄袭!

 !完结撒花!

为了试炼一下手感,写几道题目吧!

【模板】树状数组1

【模板】树状数组2

【模板】线段树1

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值