树状数组

树状数组

前导:

任何数字都可以用2的几次幂的加和来表示,例如:
\(7=2^2+2^1+2^0\)
其中,可以把这些数字通过二进制进行分段,如
\(7=[1,4],[5,6],[7,7]\)
可以通过 \(lowbit\) 运算来表示其段数,接下来是代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
	int x; cin>>x;
	while(x>0){
		printf("[%d,%d]\n",x-(x&-x)+1,x);
		x-=x&-x;
	}
	return 0;
} 

树状数组就是一种基于上述思想的数据结构。

解释

树状数组的基本用途是维护序列的前缀和

对已给定的序列 \(a\) ,我们建立一个数组 \(c\) ,其中 \(c[x]\) 保存序列 \(a\) 的区间 \([x-lowbit(x)+1,x]\) 中所有数的和

事实上,数组可以看成一个树状结构。

  1. 每个内部节点 \(c[x]\) 保存以它为根的子树中所有叶子结点的和
  2. 每个内部节点 \(c[x]\) 的子节点个数等于 \(lowbit(x)\) 的中位数
  3. 每个内部节点 \(c[x]\) 的父亲节点为 \(c[x+lowbit(x)]\)
  4. 树的深度为 \(O(logn)\)

基本操作

查询前缀和

int ask(int x){
	int ans=0;
	for(;x;x-=x&-x) ans+=c[x];//算出lowbit运算中每个根节点的加和
	return ans;
}

如果要查询 \([l,r]\) 中所有数的和,只需计算 \(ask(r)-ask(l-1)\) 即可

单点增加

给序列中的 \(a[x]\) 加上 \(y\) ,同时正确维护序列的前缀和。

void add(int x,int y){
	for(;x<=N;x+=x&-x) c[x]+=y;
}

初始化

void init(){//线性构造
    for (int i = 1; i <= n; i++){
        pre[i]=pre[i-1]+a[i];
        c[i]=pre[i]-pre[i-lowbit(i)];
    }
}

具体作用

树状数组的作用不仅是找前缀和,还有以下作用:

  1. 维护当前区间内的最大/最小值。
  2. 进行计数,能够快速算出例如一个序列中比 \(i\) 大的数一共有多少个这种问题。
  3. 对于一些题目进行优化。
posted @ 2021-10-10 11:29  Evitagen  阅读(56)  评论(0编辑  收藏  举报