首先树状数组是一种用于维护前缀信息的的数据结构,其实现十分简洁优美,如下图。
一般来说,树状数组可以解决的问题线段树也可以,但是树状数组的常数会更小。
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
...
C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16
这里有一个有趣的性质:
设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,
所以很明显:Cn = A(n – 2^k + 1) + ... + An
这里的2^k就为对应位置的lowbit(pos)
其中我们可以发现,每一个位置都对应一端长度的值,比如我们要求1到7区间的值,我们只需要依次累加d[7],d[6],d[4]的值了,这样的查询效率是我们可以接受的,它不会超过,那么如何让它累加对应位置的值呢?这时候就要引入lowbit这个神奇的东西了,lowbit是返回二进制对应末尾1最先出现的位置。那么这个有什么用呢?我们来看如果要求1到7区间的值,7对应的二进制是111,6的二进制是110,4的二进制是100,我们不难发现每次只要减去二进制最低位的1就可以实现了!!,那么设当前位置为pos,那么每次只要pos-lowbit(pos)就可以实现了。对于单个位置的数的修改,只需要修改它存在的位置就可以用了,对应位置同理为pos+lowbit(pos)直到边界。
一 单点修改,区间查询
函数lowbit()
lowbit是用来取出二进制中最低位数的1所代表的二进制的值。
代码
int lowbit(int x){
return x&(-x);
}
题目链接 https://www.luogu.org/problemnew/show/P3374
模板
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
int d[500005],n,m;
int lowbit(int x)
{
return x&(-x);
}
void add(int pos,int v)
{
while(pos<=n){
d[pos]+=v;
pos+=lowbit(pos);
}
}
void input()
{
int x;
for(int i=1;i<=n;i++){
scanf("%d",&x);
add(i,x);
}
}
int query(int x)
{ int sum=0;
while(x){
sum+=d[x];
x-=lowbit(x);
}
return sum;
}
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
printf("%d\n",query(y)-query(x-1));
}
return 0;
}
二 区间修改,单点查询
这里首先介绍差分
设数组a[]={1,6,8,5,10},那么差分数组b[]={1,5,2,-3,5}
也就是说b[i]=a[i]-a[i-1];(a[0]=0;),那么a[i]=b[1]+....+b[i];
假如区间[2,4]都加上2的话
a数组变为a[]={1,8,10,7,10},b数组变为b={1,7,2,-3,3};
发现了没有,b数组只有b[2]和b[5]变了,因为区间[2,4]是同时加上2的,所以在区间内b[i]-b[i-1]是不变的.
所以对区间[x,y]进行修改,只用修改b[x]与b[y+1]:
b[x]=b[x]+k;b[y+1]=b[y+1]-k;
之后就可以利用树状数组解决了
题目链接 https://www.luogu.org/problemnew/show/P3368
模板
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
int d[500005],n,m;
int lowbit(int x)
{
return x&(-x);
}
void add(int pos,int v)
{
while(pos<=n){
d[pos]+=v;
pos+=lowbit(pos);
}
}
int query(int x)
{ int sum=0;
while(x){
sum+=d[x];
x-=lowbit(x);
}
return sum;
}
int main()
{
scanf("%d%d",&n,&m);
int last=0,now;
for(int i=1;i<=n;i++){
scanf("%d",&now);
add(i,now-last);
last=now;
}
int f,x,y,k;
for(int i=1;i<=m;i++){
scanf("%d",&f);
if(f==1){
scanf("%d%d%d",&x,&y,&k);
add(x,k);
add(y+1,-k);
}
else{
scanf("%d",&x);
printf("%d\n",query(x));
}
}
return 0;
}
三 区间修改,区间查询
分析
c是差分数组
a[1]+a[2]+...+a[n]
= (c[1]) + (c[1]+c[2]) + ... + (c[1]+c[2]+...+c[n])
= n*c[1] + (n-1)*c[2] +... +c[n]
= n * (c[1]+c[2]+...+c[n]) - (0*c[1]+1*c[2]+...+(n-1)*c[n]) (式子①)
那么我们就维护一个数组c2[n],其中c2[i] = (i-1)*c[i]
每当修改c的时候,就同步修改一下c2,这样复杂度就不会改变
那么
式子①
=n*c1(c,n) - c2(c2,n)
题目链接 http://codevs.cn/problem/1082/
模板
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
using namespace std;
long long n,m;
long long c1[500005],c2[500005],num[500005];
long long lowbit(long long x)
{
return x&(-x);
}
void add(long long *s,long long pos,long long v)
{
while(pos<=n){
s[pos]+=v;
pos+=lowbit(pos);
}
}
long long query(long long *s,long long pos)
{
long long sum=0;
while(pos){
sum+=s[pos];
pos-=lowbit(pos);
}
return sum;
}
int main()
{ long long f,a,b,v;
scanf("%lld",&n);
num[0]=0;
for(int i=1;i<=n;i++){
scanf("%lld",&num[i]);
add(c1,i,num[i]-num[i-1]);
add(c2,i,(i-1)*(num[i]-num[i-1]));
}
scanf("%lld",&m);
for(int i=1;i<=m;i++){
scanf("%lld",&f);
if(f==1){
scanf("%lld%lld%lld",&a,&b,&v);
add(c1,a,v);
add(c1,b+1,-v);
add(c2,a,v*(a-1));
add(c2,b+1,-v*b);
}
else{
scanf("%lld%lld",&a,&b);
long long sum1=(a-1)*query(c1,a-1)-query(c2,a-1);
long long sum2=b*query(c1,b)-query(c2,b);
printf("%lld\n",sum2-sum1);
}
}
return 0;
}