树状数组
树状数组
前导:
任何数字都可以用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]\) 中所有数的和
事实上,数组可以看成一个树状结构。
- 每个内部节点 \(c[x]\) 保存以它为根的子树中所有叶子结点的和
- 每个内部节点 \(c[x]\) 的子节点个数等于 \(lowbit(x)\) 的中位数
- 每个内部节点 \(c[x]\) 的父亲节点为 \(c[x+lowbit(x)]\)
- 树的深度为 \(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)];
}
}
具体作用
树状数组的作用不仅是找前缀和,还有以下作用:
- 维护当前区间内的最大/最小值。
- 进行计数,能够快速算出例如一个序列中比 \(i\) 大的数一共有多少个这种问题。
- 对于一些题目进行优化。
不关注的有难了😠😠😠https://b23.tv/hoXKV9