转自 https://www.cnblogs.com/RabbitHu/p/BIT.html
1. 单点修改 + 区间查询
最简单的树状数组就是这样的:
void add(int p, int x){ //给位置p增加x
while(p <= n) sum[p] += x, p += p & -p;
}
int ask(int p){ //求位置p的前缀和
int res = 0;
while(p) res += sum[p], p -= p & -p;
return res;
}
int range_ask(int l, int r){ //区间求和
return ask(r) - ask(l - 1);
}
2. 区间修改 + 单点查询
通过“差分”(就是记录数组中每个元素与前一个元素的差),可以把这个问题转化为问题1。
查询
设原数组为a[i], 设数组d[i]=a[i]−a[i−1](a[0]=0),则 a[i]=∑ij=1d[j],可以通过求d[i]的前缀和查询。
修改
当给区间[l,r]加上x的时候,a[l] 与前一个元素 a[l−1] 的差增加了x,a[r+1] 与 a[r] 的差减少了x。根据d[i]数组的定义,只需给a[l] 加上 x, 给a[r+1] 减去 x即可。
void add(int p, int x){ //这个函数用来在树状数组中直接修改
while(p <= n) sum[p] += x, p += p & -p;
}
void range_add(int l, int r, int x){ //给区间[l, r]加上x
add(l, x), add(r + 1, -x);
}
int ask(int p){ //单点查询
int res = 0;
while(p) res += sum[p], p -= p & -p;
return res;
}
3. 区间修改 + 区间查询
这是最常用的部分,也是用线段树写着最麻烦的部分——但是现在我们有了树状数组!
怎么求呢?我们基于问题2的“差分”思路,考虑一下如何在问题2构建的树状数组中求前缀和:
位置p的前缀和 =∑i=1pa[i]=∑i=1p∑j=1id[j]在等式最右侧的式子∑pi=1∑ij=1d[j]中,d[1] 被用了p次,d[2]被用了p−1
次……那么我们可以写出:位置p的前缀和 =∑i=1p∑j=1id[j]=∑i=1pd[i]∗(p−i+1)=(p+1)∗∑i=1pd[i]−∑i=1pd[i]∗i
那么我们可以维护两个数组的前缀和:一个数组是 sum1[i]=d[i],另一个数组是 sum2[i]=d[i]∗i。
查询
位置p的前缀和即: (p + 1) * sum1数组中p的前缀和 - sum2数组中p的前缀和。
区间[l, r]的和即:位置r的前缀和 - 位置l的前缀和。
修改
对于sum1数组的修改同问题2中对d数组的修改。
对于sum2数组的修改也类似,我们给 sum2[l] 加上 l * x,给 sum2[r + 1] 减去 (r + 1) * x。
void add(ll p, ll x){
for(int i = p; i <= n; i += i & -i)
sum1[i] += x, sum2[i] += x * p;
}
void range_add(ll l, ll r, ll x){
add(l, x), add(r + 1, -x);
}
ll ask(ll p){
ll res = 0;
for(int i = p; i; i -= i & -i)
res += (p + 1) * sum1[i] - sum2[i];
return res;
}
ll range_ask(ll l, ll r){
return ask(r) - ask(l - 1);
}
用这个做区间修改区间求和的题,无论是时间上还是空间上都比带lazy标记的线段树要优。
转自另一个大佬https://blog.csdn.net/zars19/article/details/54620021
单点修改 区间查询
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;
int n,m;
int c[500005];
int lowbit(int x)
{
return (-x)&x;
}
void add(int pos,int x)
{
while(pos<=n)
{
c[pos]+=x;
pos+=lowbit(pos);
}
}
void input()
{
int x;
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
add(i,x);
}
}
int query(int pos)
{
int res=0;
while(pos>0)
{
res+=c[pos];
pos-=lowbit(pos);
}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
input();
int f,x,y;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&f,&x,&y);
if(f==1)
add(x,y);
else if(f==2)
cout<<query(y)-query(x-1)<<endl;
}
return 0;
}
区间修改 单点查询
#include<iostream>
#include<cstring>
#include<queue>
#include<cstdio>
using namespace std;
int c[500005];
int n,m;
int lowbit(int x)
{
return x&(-x);
}
void add(int pos,int x)
{
while(pos<=n)
{
c[pos]+=x;
pos+=lowbit(pos);
}
}
int query(int pos)
{
int res=0;
while(pos>0)
{
res+=c[pos];
pos-=lowbit(pos);
}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
int x=0,y;
for(int i=1;i<=n;i++)
{
scanf("%d",&y);
add(i,y-x);
x=y;
}
int opt,k;
for(int i=1;i<=m;i++)
{
scanf("%d",&opt);
if(opt==1)
{
scanf("%d%d%d",&x,&y,&k);
add(x,k);
add(y+1,-k);
}
else if(opt==2)
{
scanf("%d",&x);
printf("%d
",query(x));
}
}
return 0;
}
区间修改 区间查询*
此处简略地说明一下原数组a,差分数组d 则有an=∑i=1ndi所以∑i=1nai=∑i=1n∑j=1idj=∑i=1n(n−i+1)×di=(n+1)×∑i=1ndi−∑i=1ndi×i
于是我们维护两个树状数组,c1储存di,c2储存di×i
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
long long c[200005][2];
int n,q;
int lowbit(int x)
{
return x&(-x);
}
void add(int pos,int x,int f)
{
while(pos<=n)
{
c[pos][f]+=x;
pos+=lowbit(pos);
}
}
long long query(int pos,int f)
{
long long res=0;
while(pos>0)
{
res+=c[pos][f];
pos-=lowbit(pos);
}
return res;
}
long long ask(int pos)
{
long long res=(pos+1)*query(pos,0)-query(pos,1);
return res;
}
int main()
{
scanf("%d",&n);
int a=0,b,opt,x;
for(int i=1;i<=n;i++)
{
scanf("%d",&b);
add(i,b-a,0);
add(i,(b-a)*i,1);
a=b;
}
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d",&opt);
if(opt==1)
{
scanf("%d%d%d",&a,&b,&x);
add(a,x,0);
add(b+1,-x,0);
add(a,x*a,1);
add(b+1,-x*(b+1),1);
}
else if(opt==2)
{
scanf("%d%d",&a,&b);
printf("%lld
",ask(b)-ask(a-1));
}
}
return 0;
}