树状数组简介
在了解树状数组之前我们首先要明确树的概念,树是一种数据结构,它是由有限个节点组成的有层次关系的集合,把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
每个节点有零个或多个子节点;没有父节点的节点称为根节点;每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子树。
树状数组首先它是一个数组,但是它使用了树的结构,如下图所示
A为原数组,上面的C数组就是我们所说的树状数组,A数组的值按图中的规律存在树状数组C中。这样做的好处是当我们需要对一个区间进行多次操作时可以大大减少时间复杂度。
树状数组的实现
Lowbit函数
这个函数是所有对树状数组进行操作的基本函数,它的作用是获得sum函数在x节点处存的数的个数,代码如下:
int Lowbit (int x){
return x&(-x);
}
sum函数
用于获得区间[1,x]的和,
Sum中x节点存的数的个数是lowbit(x),
C[x]=A[x-lowbit(x)+1] + A[x-lowbit(x)+2] +..+A[x-1]+A[x]
所以结果加上了C[x]之后,紧接着是要去找C[x-lowbit(x)]的值,于是不难看懂如下Sum代码:
int Sum(int x){//前x项的和
int ANS=0;
while(x>0){
ANS+=C[x];
x-=lowbit(x);
}
return ANS;
}
update函数(也叫add函数)
用于对树状数组的某个点改变值操作
void update (int i,int x){ //i点增加x
while (i <= N){ //N为原数组长度
tree[i] += x;
i += Lowbit(i);
}
}
树状数组的应用
区间修改,单点查询
思想其实是化区间修改为点修改,不使用C[i]数组了。
增加B数组。假设B[i]=t , 则表示对区间A[i],A[i+1],...,A[n]都增加了t.
那么,把区间[L,R]的数都加上X的操作就变成了两个点修改:B[L]+=X 和 B[R+1]-=X;//这里是简写,还需要更新它们的父节点
然后查询点X的时候,要求B[1]+B[2]+...+B[X]的和再加上A[X],因为这些点上的修改都会影响到X.
区间修改,区间查询
这里就用到一点数学了,用a[i]表示A[i]-A[i-1](假设A[0]=0)
用S[i]表示a[1]+a[2]+...+a[i] ,用 P[i] 表示 a[1]+2*a[2]+3*a[3]+...+i*a[i]
则前缀和的前缀和 SS[x]=S[1]+S[2]+S[3]+...+S[x]=n*a[1]+(n-1)*a[2] +...+2*a[x-1] + a[x] = (n+1)S[x]- P[x]
于是只要对差分数列维护S[i]和P[i]两个性质就好了。
S[i]维护原数组为a[i]的前缀和(=A[i]),P[i]维护原数组为 i*a[i]的前缀和
区间修改的话,A[L]-A[L-1]多了X。A[R+1]-A[R] 少了X。即 a[L]+=X. a[R+1]-=X; 两个点修改
每个点都要修改S和P两个数组。
#define LL long long
#define maxn 100001
int N,Q;
LL A[maxn];
LL P[maxn],S[maxn];
void AddP(LL x,LL v){
if(!x) return;
while(x <=N){
P[x]+=v;
x+=x&-x;
}
}
void AddS(LL x,LL v){
if(!x) return;
while(x <=N){
S[x]+=v;
x+=x&-x;
}
}
LL SumP(LL x){
LL ans=0;
while(x>0){
ans+=P[x];
x-=x&-x;
}
return ans;
}
LL SumS(LL x){
LL ans=0;
while(x>0){
ans+=S[x];
x-=x&-x;
}
return ans;
}
void INC(LL L,LL R,LL C){//区间[L,R]都加C
AddS(L,C);AddS(R+1,-C);
AddP(L,L*C);AddP(R+1,-(R+1)*C);
}
LL QUE(LL a){//询问前缀和
return (a+1)*SumS(a)-SumP(a);
}