相信很多同学都已经感受过了线段树的冗杂 细致精妙,老实说,我觉得那些奇长无比的代码很令人崩溃 愉悦(说真的,当我第一次接触树状数组发现30行代码可以AC90行代码的线段树模板题时,我是崩溃的),主要是有些细节比较容易出问题,各种函数的作用和功能划分比较细,一道板子题常常要上百行代码才能实现(我不喜欢压行),调试起来很麻烦,对于一些较为简单的类板子题用什么比较简单方便呢?这就是今天要介绍的树状数组了。
有请我们的主角闪亮登场(撒花花~撒花花)!
好了,在开讲讲我们先肃清一下:树状数组不是树!树状数组不是树!
由于此概念的重要性,我们再emphasise一下:树状数组不是树!
或者我们可以这么理解,树状数组是一种求前缀和以及对前缀和进行高效操作的数据结构,其效率也是log级的。
而且,我们想要维护的,也就是前缀和,这一点相当重要。
在树状数组的学习中,对上面两张图的理解是至关重要的,个人认为第二张(只用看左边就好,右边带黄颜色的那一半有点迷)会比较好理解,接下来的解释也请大家多查一查上面的表方便理解,请大家一定注意。
lowbit的定义
inline int lowbit(int x){
return x&-x;
}
这是一种神奇的运算,可谓是树状数组的灵魂。它的神奇之处在于它与前缀和的维护有着微妙的关系,使得查询与修改都很方便。
先说一下数组c,对于数组c[ ],其中的节点i维护的都是原数组a[ ]中节点i本身与它前lowbit(i)-1个节点的区间和,也就是区间【i-lowbit(i)+1,i】的总和。比如lowbit(6)=2,那么c[6]维护的就是a[5]+a[6]的值; lowbit(8)=8,那么c[8]维护的就是a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]的值。
那么妙在何处呢?由上面的图2我们可以直观地感受到,有的线段是被其他线段完全覆盖的,而且我们发现,对于c[i],离它最近的一个可以完全覆盖它的线段恰好是c[i+lowbit(i)]。
所以当我们修改a[i]并维护前缀和时,a[i]改变,首先改变的便是c[i],然后改变的就是所有覆盖了c[i]的线段:c[i+lowbit(i)],c[(i+lowbit(i))+lowbit(i+lowbit(i))]…不断地向上递推一直到上界就OK了。
查询又怎么办呢?
我们已经讲过,c[i]维护的是a[i-lowbit(i)+1]~a[i]的区间总和,那么也就是说,离c[i]最近的c[i]的力不能及之处就是c[i-lowbit(i)],也就是说,c[i]与c[i-lowbit(i)]覆盖的区间是没有重合的,而且也恰好没有漏掉a[i-lowbit(i)] ~a[i]间任何一个数。举个栗子, lowbit(6)=2,6-2=4,而恰好,c[6]+c[4]的值就是a[1]+a[2]+a[3]+a[4]+a[5]+a[6],这一点在上图中的直观感受就是线段c[6]和线段c[4]拼在一起就刚好覆盖完了a[1]到a[6]的所有点,且不重不漏。
至于查询a[i]~ a[j]的区间和,学了差分数组的普及组小朋友都知道该怎么做了吧 直接用树状数组查出位置j的前缀和与位置i-1的前缀和,二者一减就行了。
奉上一维树状数组的单点修改与查询代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m;
inline int read(){
char ch;int flag=1;
ll ans;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1;
ans=ch-48;
while((ch=getchar())>='0'&&ch<='9') ans=ans*10+ch-48;
return ans*flag;
}
int c[100001];
inline int lb(int x){
return x&(-x);
}
inline void update(int k,int v){
while(k<=n){
c[k]+=v;
k=k+lb(k);
}
}
inline ll query(int k){
ll ret=0;
for(;k>0;k-=lb(k)) ret+=c[k];
return ret;
}
int main(){
n=read(),m=read();
char c;
for(ll i=1;i<=m;i++){
int x,y;
cin>>c>>x>>y;
if(c=='C') {
update(x,y);
}
else{
printf("%lld\n",query(y)-query(x-1));
}
}
return 0;
}
#include<bits/stdc++.h>
using namespace std;
#define int long long
//二维的树状数组区间修改与区间查询
inline int read(){
char ch;
int ans,flag=1;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1;
ans=ch-48;
while((ch=getchar())>='0'&&ch<='9') ans=ans*10+ch-48;
return ans*flag;
}
int n,m,q;
int t1[2050][2050],t2[2050][2050],t3[2050][2050],t4[2050][2050];
inline int lb(int x){
return x&(-x);
}
inline void update(int x,int y,int v){
for(int i=x;i<=n;i+=lb(i)){
for(int j=y;j<=m;j+=lb(j)){
t1[i][j]+=v;
t2[i][j]+=v*x;
t3[i][j]+=v*y;
t4[i][j]+=v*x*y;
}
}
}
inline int query(int x,int y){
int ans=0;
for(int i=x;i>0;i-=lb(i)){
for(int j=y;j>0;j-=lb(j)){
ans+=(x+1)*(y+1)*t1[i][j]-(y+1)*t2[i][j]-(x+1)*t3[i][j]+t4[i][j];
}
}
return ans;
}
inline void sum(int a,int b,int c,int d,int v){
update(a,b,v);update(a,d+1,-v);
update(c+1,b,-v);update(c+1,d+1,v);
}
inline int solve(int a,int b,int c,int d){
int ans=query(c,d)-query(c,b-1)-query(a-1,d)+query(a-1,b-1);
return ans;
}
int f,a,b,c,d,v;
signed main(){
n=read(),m=read();
while(~scanf("%d%d%d%d%d",&f,&a,&b,&c,&d)){
if(f==1){
v=read();
sum(a,b,c,d,v);
}
else{
cout<<solve(a,b,c,d)<<"\n";
}
}
return 0;
}