树状数组入门超详解

1,467 阅读3分钟

目录

前言

原理

query

modify

模板

实际作用

前言

首先提出一个问题:对于一个长度为n的序列,进行以下操作:

  • 给下标为x的位置的数加上c,简称 modify
  • 求区间 [ L, R ] 的和 ,简称 query

有下列几种方法可以实现

  1. 利用数组实现操作,modify的时间复杂度为O(1),query的时间复杂度为O(N)
  2. 利用前缀和数组,modify的时间复杂度为O(N),query的时间复杂度为O(1)

如果题中要求进行m次modify或者query操作,那么时间复杂度就会变成O(n*m),很多情况下会超时;于是,树状数组站出来了;

树状数组对这两种操作的时间复杂度进行了折中处理

  • modify复杂度为O(log n)
  • query 复杂度为O(log n)

原理

任何一个 十进制数 都能用 二进制数 来表示

eg: X=2^{i_k}+2^{i_{k-1}}+2^{i_{k-2}}+2^{i_{k-3}}+...+2^{i_1}​ (i_{k}\geq i_{k-1}\geq i_{k-2}\geq i_{k-3}\geq ...\geq i_{1}​)

那么区间 [1,x] 可以划分为以下几个连续的区间:

  • (x-2^{i_1}​ , x] 区间长度为 2^{i_1}
  • (x-2^{i_{2}}​-2^{i_1}​ , x-2^{i_1}​] 区间长度为 2^{i_{2}}
  • (x-2^{i_{3}}​-2^{i_{2}}​-2^{i_1}​ , x-2^{i_{2}}​-2^{i_1}​] 区间长度为 2^{i_{3}}
  • .........
  • (x-2^{i_{k}}​-2^{i_{k-1}}​-...-2^{i_{3}}​-2^{i_{2}}​-2^{i_1}​ , x-2^{i_{k-1}}​-...-2^{i_{3}}​-2^{i_{2}}​-2^{i_1}​ ] 区间长度为 2^{i_k}

由上述区间可知,每个区间的长度就是区间右端点R的 lowbit(R)

(就是R的二进制的最低为的1所代表的2次幂)

所以上述任何一个区间 ( L , R ] 都可以表示为 [R-lowbit(R)+1, R]

设 c[R] 为 以 R 结尾,长度是 2^{i_{k}}​ 的区间和

2^{i_{k}}

就是lowbit(R))

则可得以下树状数组的图解(其中下方为原数组a的下标)

query

拿求区间 sum=[1,12] 的和举个例子;由图例可知 sum= c[12] + c[8]

又因为8=12-lowbit(12),所以 sum= c[12] + c[12-lowbit(12)]

于是推出区间求和操作 sum=[1,R]= c[R] + c[R-lowbit(R)] + c [ temp - lowbit(temp)]+....

(temp=R-lowbit(R))

int query(int x)//查询区间1~x的区间和;
{
	int res=0;
	for(int i=x;i>=1;i-=lowbit(i)) res+=tr[i];
	return res; 
}

modify

那么更改位置x的数值以后,怎样更新区间的数值呢?

eg:更新a[5]位置 的值,执行操作a[5]+=c;

  • 首先a[5]这个位置要加上c,也就是 c[5]+=c
  • 然后找子节点和父节点对应关系:c[5]对应的父节点为 c[6] ,c[6]对应的父节点为c[8], c[8]对应的父节点为c[16]
  • 也就是 5 ——> 6 ——> 8 ——> 16
  • 转化关系为 6 =5+lowbit(5) ; 8 =6+lowbit(6) ; 16=8+lowbit(8)

所以子节点更新父节点的方式就是,每次加上自身的lowbit

void modify(int x,int c)//修改树状数组x位置的值
{
	for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}

上述就是树状数组的基本原理,对于由父节点找子节点,由子节点找父节点的二进制原理,以及lowbit的基本原理,这里没有介绍;感兴趣的同学可以去b站找视频或者csdn等了解下;

模板

int lowbit(int x)
{
	return x&-x;
}
void modify(int x,int c)//修改树状数组x位置的值
{
	for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}
int query(int x)//查询区间1~x的区间和;
{
	int res=0;
	for(int i=x;i>=1;i-=lowbit(i)) res+=tr[i];
	return res; 
}

实际作用

简单说下树状数组能解决的问题:

  • 单点修改,区间求和(利用模板即可)
  • 区间修改,单点查询(做差分树状数组)
  • 区间修改,区间求和(做差分树状数组,做前缀树状数组)

(能用树状数组解决的问题都能用线段树解决,树状数组原理难理解,但代码简单;线段树原理简单,代码长)