001 树状数组基础
数据结构-——树状数组杂谈
首先,明确树状数组的功能,简单来说,树状数组就是一种支持单点修改,区间查询的数据结构。树状数组的工作基于唯一分解性质,通过将数组拆分成2的次幂大小的区间来完成高效查询操作,可以高效解决前缀和和逆序对问题。
工作机理:区间P[1,x]可以拆分成logx个小区间,如上所述,以2的次幂为大小的区间。知晓了基本要求,那么如何确定这些小区间呢?引入lowbit运算,简单的说,lowbit运算就是取出指定数的最低(1)位。举个例子,对5(101)进行lowbit运算,取出(001);对8(1000)进行lowbit运算,取出(1000)。lowbit的运算规则lowbit(x)=(x)&(-x)。通过lowbit运算,我们可以得到每个小区间,具体的说
while(x>0) { printf("%d %d",x-(x&-x)+1,x); x-=x&-x; }
存在c[x]数组,存储前缀和,c[x]运算机理如图所示。
上面提到,树状数组支持区间查询,具体来说,查询操作
long long query(int x) { long long ans=0; for(;x;x-=(x&-x)) ans+=c[x]; return ans; }
单点修改
void add(int x,int k) { for(;x<=n;x+=(x&-x)) c[x]+=k; }
初始化操作:按需即可,若是初始化前缀和,那么直接对每个点单点操作即可,即可建起树状数组
树状数组工作原理(图片来源于oi-wiki)

https://www.luogu.com.cn/problem/P3374 例题代码实现
#include<bits/stdc++.h> using namespace std; const int N=5e5+5; long long a[N],c[N],b[N],n,m; void add(int x,int k) { for(;x<=n;x+=(x&-x)) c[x]+=k; } long long query(int x) { long long ans=0; for(;x;x-=(x&-x)) ans+=c[x]; return ans; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i],add(i,a[i]); for(int i=1;i<=m;i++) { int op; cin>>op; if(op==1) { int x,k; cin>>x>>k; add(x,k); } else { int x,y; cin>>x>>y; cout<<query(y)-query(x-1)<<"\n";//注意查询值 } } return 0; }
探讨:那么树状数组能不能实现区间修改呢?答案很简单,不可以。一般来说,区间修改都要使用线段树,那么我们能不能通过鬼脑想到怎么完成树状数组的区间修改呢,答案是可以的。也就是说,我们可以用树状数组来维护一个差分数组。
这道变式题也就是区间修改,单点查询。
P3368 【模板】树状数组 2
采用精妙的差分数组的形式就可以完成树状数组区间的修改,简单来说,就是把原数组的值不加入c[x]当中,转而维护增加的值。这样就可以搞定区间修改的难题。
#include<bits/stdc++.h> using namespace std; const int N=5e5+5; long long a[N],c[N],n,m; void add(int x,int y) { for(;x<=n;x+=(x&-x)) { c[x]+=y; } } long long query(int x) { long long ans=0; for(;x;x-=(x&-x)) ans+=c[x]; return ans; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=m;i++) { int op; cin>>op; if(op==1) { int x,y,k; cin>>x>>y>>k; add(x,k); add(y+1,-k); } if(op==2) { int x; cin>>x; cout<<a[x]+query(x)<<"\n"; } } return 0; }

浙公网安备 33010602011771号