先膜黑科技讲义 - Magolor orz
基础
设lowbit(x)表示的是把x的二进制只留下最低一位的1,然后lowbit(x)=x&(-x) (我也不知道为什么)
设c[x]表示从i往前一共lowbit(x)个数的和,那么x-lowbit(x)就是c[x]表示的范围的前一个数。
然后可以得到c[x]=c[x-lowbit(x)+lowbit(x)>>1]+c[x-lowbit(x)+lowbit(x)>>1+lowbit(x)>>2]+c[x-lowbit(x)+lowbit(x)>>1+lowbit(x)>>2+lowbit(x)>>3]....+c[x-1]+a[x]。
也就是说,如果将这个关系做成一个树,x的父亲就是x+lowbit(x)(观察一下上面的式子,很容易发现每一项都满足)
要询问1到x的和的话,只要一直加c[x],然后让x-=lowbit(x)直到x=0就好了
然后要修改一个点x的话,就一直往上修改它的父亲直到x>n就可以了。
也就是说,最基本的树状数组支持的是单点修改和前缀和查询,然后前缀和可以很容易地做成区间和(端点减一减)。
luogu3374
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define lowbit(x) ((x)&(-(x))) 5 #define LL long long int 6 const int maxn=500050; 7 8 inline int rd(){ 9 int x=0;char c=getchar();int neg=1; 10 while(c<'0'||c>'9'){if(c=='-') neg=-1;c=getchar();} 11 while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); 12 return x*neg; 13 } 14 15 int N,M; 16 LL c[maxn]; 17 18 inline void add(int x,int y){ 19 while(x&&x<=N) c[x]+=y,x+=lowbit(x); 20 } 21 inline LL query(int x){ 22 LL re=0;while(x) re+=c[x],x-=lowbit(x);return re; 23 } 24 25 int main(){ 26 int i,j,k; 27 N=rd(),M=rd(); 28 for(i=1;i<=N;i++) add(i,rd()); 29 for(i=1;i<=M;i++){ 30 int a=rd(),b=rd(),c=rd(); 31 if(a==1) add(b,c); 32 else printf("%lld ",query(c)-query(b-1)); 33 } 34 }
也可以支持区间修改和单点查询,只要做一个差分,区间修改就变成了两个端点的修改,单点查询就变成了前缀和查询。
luogu3368
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define lowbit(x) ((x)&(-(x))) 5 #define LL long long int 6 const int maxn=500050; 7 8 inline int rd(){ 9 int x=0;char c=getchar();int neg=1; 10 while(c<'0'||c>'9'){if(c=='-') neg=-1;c=getchar();} 11 while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); 12 return x*neg; 13 } 14 15 int N,M; 16 LL c[maxn]; 17 18 inline void add(int x,int y){ 19 while(x&&x<=N) c[x]+=y,x+=lowbit(x); 20 } 21 inline LL query(int x){ 22 LL re=0;while(x) re+=c[x],x-=lowbit(x);return re; 23 } 24 25 int main(){ 26 int i,j,k; 27 N=rd(),M=rd(); 28 for(i=1,j=0;i<=N;i++){ 29 k=rd();add(i,k-j);j=k; 30 }; 31 for(i=1;i<=M;i++){ 32 int a=rd(),b=rd(); 33 if(a==1){ 34 int c=rd(),d=rd(); 35 add(b,d);add(c+1,-d); 36 } 37 else printf("%lld ",query(b)); 38 } 39 }
升级
区间修改和区间查询也是可以支持的,然后我选择线段树......
树状数组写起来真方便,suki
$$前n项和sum_{i=1}^{n}{a[i]}=sum_{i=1}^{n}{sum_{j=1}^{i}{d[j]}}(差分数组)=sum_{i=1}^{n}{(n-i+1)d[i]}=(n+1)sum_{i=1}^{n}{d[i]}-sum_{i=1}^{n}{i*d[i]}$$
这样的话,只要同时维护d[i]和i*d[i],就可以做到区间修改和区间查询了。
luogu3372
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<vector> 5 #include<queue> 6 #include<map> 7 #include<cmath> 8 #include<ctime> 9 #include<set> 10 #define pa pair<int,int> 11 #define lowb(x) ((x)&(-(x))) 12 #define REP(i,n0,n) for(i=n0;i<=n;i++) 13 #define PER(i,n0,n) for(i=n;i>=n0;i--) 14 #define MAX(a,b) ((a>b)?a:b) 15 #define MIN(a,b) ((a<b)?a:b) 16 #define CLR(a,x) memset(a,x,sizeof(a)) 17 #define rei register int 18 using namespace std; 19 typedef long long ll; 20 const int maxn=100010; 21 22 ll rd(){ 23 ll x=0;char c=getchar();int neg=1; 24 while(c<'0'||c>'9'){if(c=='-') neg=-1;c=getchar();} 25 while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); 26 return x*neg; 27 } 28 29 int N,M; 30 ll tr[maxn],itr[maxn]; 31 32 inline void add(int x,ll y){ 33 ll yy=y*x; 34 while(x<=N){ 35 tr[x]+=y;itr[x]+=yy;x+=lowb(x); 36 } 37 } 38 inline ll query(int x){ 39 ll re=0,n=x; 40 while(x){ 41 re+=(n+1)*tr[x]-itr[x];x-=lowb(x); 42 }return re; 43 } 44 45 int main(){ 46 rei i,j,k; 47 N=rd(),M=rd();ll lst=0; 48 for(i=1;i<=N;i++){ 49 ll x=rd();add(i,x-lst);lst=x; 50 }for(i=1;i<=M;i++){ 51 int a=rd(),b=rd(),c=rd(); 52 if(a==1){ 53 ll k=rd();add(b,k);add(c+1,-k); 54 }else{ 55 printf("%lld ",query(c)-query(b-1)); 56 } 57 } 58 return 0; 59 }
二维的话也是可以做的,就相当于每跳到一个x都跳一遍y的lowbit,而且可以支持区间修改和区间查询
单点修改区间查询不多说,来说说区间修改和查询
仿照一维的形式,我们先得到差分数组
因为差分数组的前缀和就是原来那个点的值,所以能得到$d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]$
所以如果是(x1,y1,x2,y2)区间加的话,就是给(x1,y1),(x2+1,y2+1)加,给(x1,y2+1),(x2+1,y1)减(手画一下)
然后是查询,也是有$(x1,y1,x2,y2)=S[x2][y2]-S[x1-1][y2]-S[x2][y1-1]+S[x1-1][y1-1]$
然后有
$$S[x][y]=sumlimits_{i=1}^{x}{sumlimits_{j=1}^{y}{sumlimits_{k=1}^{i}{sumlimits_{l=1}^{j}{d[k][l]}}}}$$
$$=sumlimits_{i=1}^{x}{sumlimits_{k=1}^{i}{sumlimits_{j=1}^{y}{((y+1)d[k][j]-j*d[k][j])}}}$$
$$=sumlimits_{i=1}^{x}{sumlimits_{j=1}^{y}{(x+1)(y+1)d[i][j]-(x+1)*j*d[i][j]-(y+1)*i*d[i][j]+i*j*d[i][j]}}$$
于是二维树状数组维护d,id,jd和ijd就行了
高级用法以后慢慢填