树状数组入门超详解

目录

前言

原理 

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的下标)

树状数组入门超详解_第1张图片

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; 
}

实际作用 

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

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

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

 

你可能感兴趣的:(#,树状数组)