一、树状数组
<1>、普通数组
以普通数组为例:
单点修改 时间复杂度为O(1),分段查询 时间复杂度为O(n)。
以普通数组的前缀和为例:
分段查询 时间复杂度为O(1),单点修改 时间复杂度为O(n)。(改一个值影响后面的前缀和)。
<2>、树状数组
1、原理
如图,每个树状数组管理着 2的k次方个数字(此数字的二进制表达的末尾0的个数k)
例如:
d[6]=a[6]+a[5]
110 2^1
d[8]=a[1]+...+a[8]
1000 2^3
2、询问
若要询问14的前缀和:
d[14]+d[12]+d[8];
同理询问11的前缀和:
d[11]+d[10]+d[8];
保证了查询复杂度为O(logn)
若查询13的前缀和
1101=13;有3个1
1101 d[13] 管辖2^0
1100 d[12] 管辖2^2
1000 d[8] 管辖2^3
管辖总数为13
则每次抹掉末尾1的个数
由上得树状数组单词查询复杂度为O(logn)
3、修改
若要修改6的值
则d[16]、d[8]、d[6]都需要修改//详情见上图
则将末尾1处加1
110=6
11+1=100再加上原本末尾的0
1000=8
同理
1+1=10再加000
10000=16
再例:
1101=13
1110=14
10000=16
4、lowbit
由上述可知,增加和删除 (末尾1及后面的0的十进制数),故此用到lowbit方法
lowbit(int x){
return x&(-x);
}
/*
1100 = 12
补码0011+1=0100
1100
&
0100
=
0100
*/
10100
01011+1=01100
只找到末尾1,取反将后面的0变为1,再加一,而末尾1之前的数字&操作之后则无关
10100 -> 01011 -> 01100
5、实现
int d[100005];
int lowbit(int x){
return x&(-x);
}
int query(int x){//查询
int res=0;
while(x){
res+=d[x];
x-=lowbit(x);
}
return res;
}
void update(int x,int v){//修改,在a[x]加v
while(x<=n){
d[x]+=v;
x+=lowbit(x);
}
}
6、拓展
区间修改/单点查询:
代码不变
若要在3-6区间每个加5
只需在3处+5,在7处-5即可,此时query变成单点查询。
即可完成区间修改和单点查询
区间最值:更推荐线段树
<3>、二维树状数组
解决:单点修改+sum[x][y]
//从1到x行中每一行前y个数字的和。
解决:子矩阵的和
解决:几个子矩阵的差或和。
int d[301][301];
void update(int x,const int &y,const int &v){
for(;x<=n;x+=(x&(-x)))
for(int j=y;j<=n;j+=(j&(-j)))
d[x][j]+=v;
}
int getsum(int x,const int &y){
int res=0;
for(;x;x-=(x&(-x)))
for(int j=y;j;j-=(j&(-j)))
res+=d[x][j];
return res;
}