开这篇文的目的是数据结构的代码太长,放在一篇里面就太冗杂了,所以就再开一篇记录各种模板。
本质:动态维护前缀和。当然,也可以维护前缀最小,最大
前缀和是最常用的,最值只适用于特定情况
lowbit()//lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1和后面的0,
比如10的二进制是1010,则lowbit(k) = lowbit(1010)
单点修改update()
前缀和查询query()
区间查询ask()
区间修改change()
结合差分思想
ll Lowbit(int x){return x & (-x);}
t[x]中储存的就是右端点为x,长为lowbit(x)的区间a[]的和。
void update(int x,int y){
while(x <= n){
t[x] += y;
x += Lowbit(x);//上加下减
}
}
ll query(int x){//查询1到x的前缀和
ll ret = 0;
while(x){
ret += t[x];
x -= Lowbit(x);
}
return ret;
}
int ask(int l,int r){
return query(r)-query(l-1);//如果是单点查询,就是(l,l);
}
通过差分,用树状数组来维护a的差分数组d[] ( d[i]=a[i]-a[i-1] ,a[0]=0)
修改:则 在修改a[l]->a[r]时,对d的影响仅在a[l-1],a[l]和a[r],a[r+1]两处。
所以只需修改d[l]和d[r+1]两处,使l位置+x,r+1位置-x即可。
查询:通过d的前缀和查询即可。
int update(int x,int y){
while(x<=n){t[x]+=y;x+=lowbit(x);}
}
int query(int x){
int ret=0;
while(x){ret+=t[x];x-=lowbit(x);}
return ret;
}
int change(int l,int r,int z){
update(l,z);update(r+1,-z);
}
int ask(int x){
query(x);//因为是树状数组维护的是前缀和,所以就直接是a[x]=query(x);
}
我们考虑对树状数组维护前缀和
∑ i = 1 p a [ i ] = ∑ i = 1 p ∑ j = 1 i d [ j ] \sum_{i=1}^{p}a[i]=\sum_{i=1}^{p}\sum_{j=1}^{i}d[j] ∑i=1pa[i]=∑i=1p∑j=1id[j]
把d[j]提出来: = ∑ i = 1 p ( p − i + 1 ) ∗ d [ i ] =\sum_{i=1}^{p}(p-i+1)*d[i] =∑i=1p(p−i+1)∗d[i]
用类似反演里的公式:重点
= ∑ i = 1 p ( p + 1 ) ∗ d [ i ] − ∑ i = 1 p i ∗ d [ i ] =\sum_{i=1}^{p}(p+1)*d[i]-\sum_{i=1}^{p}i*d[i] =∑i=1p(p+1)∗d[i]−∑i=1pi∗d[i]
所以这里我们维护两个基本数组:
s u m 1 [ i ] = d [ i ] sum1[i]=d[i] sum1[i]=d[i]和 s u m 2 [ i ] = i ∗ d [ i ] sum2[i]=i*d[i] sum2[i]=i∗d[i].
void update(int x,int y){
while(x<=n){
sum1[x]+=y;
sum2[x]+=x*y;
x+=lowbit(x);
}
}
int query(int x){
int px=x,ret=0;
while(x){
ret+=sum1[x]*px-sum2[x];
x-=lowbit(x);
}
return ret;
}
int ask(int l,int r){
return query(r)-query(l-1);
}
int change(int l,int r,int z){
update(l,z);update(r+1,-z);
}
t[x][y]表示右下角端点为(x,y)的长为lowbit(x),宽为lowbit(y)的矩阵的和。
其实就是一维变二维了。
void update(int x,int y,int z){
int py=y;
while(x<=n){
y=py;
while(y<=n){
t[x][y]+=z;
y+=lowbit(y);
}
x+=lowbit(x);
}
}
int query(int x,int y){
int py=y,ret=0;
while(x){
y=py;
while(y){
ret+=t[x][y];
y-=lowbit(y);
}
x-=lowbit(x);
}
return ret;
}
int ask(int l1,int r1,int l2,int r2){
return query(l2,r2)-query(l2,r1)-query(l1,r2)+query(l1,r1);
}
同一维树状数组,我们此时维护的是 d [ i ] [ j ] = a [ i ] [ j ] − a [ i − 1 ] [ j ] − a [ i ] [ j − 1 ] + a [ i − 1 ] [ j − 1 ] d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1] d[i][j]=a[i][j]−a[i−1][j]−a[i][j−1]+a[i−1][j−1]
对于每次修改,不同的是四个位置。
void update(int x,int y,int z){
int py=y;
while(x<=n){
y=py;
while(y<=n){t[x][y]+=z;y+=lowbit(y);}
x+=lowbit(x);
}
}
int query(int x,int y){//树状数组动态维护前缀和,此处直接就是a[x][y]的值。
int py=y,ret=0;
while(x){
y=py;
while(y){ret+=t[x][y];y-=lowbit(y);}
x-=lowbit(x);
}
return ret;
}
void change(ll l1,ll l2,ll r1,ll r2,ll z){
update(r1+1,r2+1,z);update(r1+1,l2,-z);update(l1,r2+1,-z);update(l1,l2,z);
}
**查询:**所求:
∑ i = 1 n ∑ j = 1 m a [ i ] [ j ] = ∑ i = 1 n ∑ j = 1 m ∑ x = 1 i ∑ y = 1 j d [ x ] [ y ] = ∑ i = 1 n ∑ j = 1 m ( n − i + 1 ) ∗ ( m − j + 1 ) ∗ d [ i ] [ j ] \sum_{i=1}^{n}\sum_{j=1}^{m}a[i][j]=\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{x=1}^{i}\sum_{y=1}^{j}d[x][y]=\sum_{i=1}^{n}\sum_{j=1}^{m}(n-i+1)*(m-j+1)*d[i][j] ∑i=1n∑j=1ma[i][j]=∑i=1n∑j=1m∑x=1i∑y=1jd[x][y]=∑i=1n∑j=1m(n−i+1)∗(m−j+1)∗d[i][j]
然后展开
( n − i + 1 ) ∗ ( m − j + 1 ) ∗ d [ i ] [ j ] = ( n + 1 ) ∗ ( m + 1 ) ∗ d [ i ] [ j ] − ( n + 1 ) ∗ j ∗ d [ i ] [ j ] − ( m + 1 ) ∗ i ∗ d [ i ] [ j ] + d [ i ] [ j ] ∗ i ∗ j (n-i+1)*(m-j+1)*d[i][j]=(n+1)*(m+1)*d[i][j]-(n+1)*j*d[i][j]-(m+1)*i*d[i][j]+d[i][j]*i*j (n−i+1)∗(m−j+1)∗d[i][j]=(n+1)∗(m+1)∗d[i][j]−(n+1)∗j∗d[i][j]−(m+1)∗i∗d[i][j]+d[i][j]∗i∗j
所以需要这四个:
( n + 1 ) ∗ ( m + 1 ) ∑ i = 1 n ∑ j = 1 m d p [ i ] [ j ] (n+1)*(m+1)\sum_{i=1}^{n}\sum_{j=1}^{m}dp[i][j] (n+1)∗(m+1)∑i=1n∑j=1mdp[i][j]
( n + 1 ) ∑ i = 1 n ∑ j = 1 m j ∗ d [ i ] [ j ] (n+1)\sum_{i=1}^{n}\sum_{j=1}^{m}j*d[i][j] (n+1)∑i=1n∑j=1mj∗d[i][j]
( m + 1 ) ∑ i = 1 n ∑ j = 1 m i ∗ d [ i ] [ j ] (m+1)\sum_{i=1}^{n}\sum_{j=1}^{m}i*d[i][j] (m+1)∑i=1n∑j=1mi∗d[i][j]
∑ i = 1 n ∑ j = 1 m i ∗ j ∗ d [ i ] [ j ] \sum_{i=1}^{n}\sum_{j=1}^{m}i*j*d[i][j] ∑i=1n∑j=1mi∗j∗d[i][j]
所以维护四个基本数组
sum1[i][j]=d[i][j]
sum2[i][j]=i*d[i][j]
sum3[i][j]=j*d[i][j]
sum4[i][j]=ijd[i][j]
void update(ll x,ll y,ll z){
ll px=x,py=y;
while(x<=n){
y=py;
while(y<=m){
sum1[x][y]+=z;
sum2[x][y]+=z*px;
sum3[x][y]+=z*py;
sum4[x][y]+=z*px*py;
y+=lowbit(y);
}
x+=lowbit(x);
}
}
ll query(ll x,ll y){
ll px=x,py=y,ret=0;
while(x){
y=py;
while(y){
ret+=(px+1)*(py+1)*sum1[x][y]-(py+1)*sum2[x][y]-(px+1)*sum3[x][y]+sum4[x][y];
y-=lowbit(y);
}
x-=lowbit(x);
}
return ret;
}
void change(ll l1,ll l2,ll r1,ll r2,ll z){
update(r1+1,r2+1,z);update(r1+1,l2,-z);update(l1,r2+1,-z);update(l1,l2,z);
}
ll ask(ll l1,ll l2,ll r1,ll r2){
return query(r1,r2)-query(r1,l2-1)-query(l1-1,r2)+query(l1-1,l2-1);
}
树状数组仅能在单点修改,区间查询求最值:
设原数序列为a[],树状数组为t[].
修改是要修改每个包含x的区间。因为x向上增的lowbit()是固定的,所以每次就以当前点为终点,枚举每个起点,更改树状数组的值。
查询是很好理解的,就是向倍增一样的不断往前靠。
void update(int x){
while(x<=n){
t[x]=a[x];
int lx=lowbit(x);
for(int i=1;i=x){
ret=max(a[y],ret);
y--;
for(;y-lowbit(y)>=x;y-=lowbit(y))
ret=max(ret,t[y]);
}
return ret;
}
时间复杂度均为 O ( l o g 2 n ) O(log^2{n}) O(log2n)
一、正整数的原码、反码、补码完全一样,即符号位固定为0,数值位相同
二、负整数的符号位固定为1,由原码变为补码时,规则如下:
1、原码符号位1不变,整数的每一位二进制数位求反,得到反码
2、反码符号位1不变,反码数值位最低位加1,得到补码
树状数组维护的是前缀和。
修改矩阵中(x1,y1)->(x2,y2)中的所有值。
修改四个点:
a[x1][y1]+=p;a[x2+1][y2+1]+=p;
a[x1+1][y1]-=p;a[x1][y2+1]-=p;
然后跑二维前缀和即可。
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
参考:https://www.cnblogs.com/RabbitHu/p/BIT.html