int tree[maxn];
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,int val){ //将x处的值增加val
for(int i=x;i<maxn;i+=lowbit(i)){
tree[i]+=val;
}
}
inline int query(int x){ //求[1,x]的所有值的和
int ans=0;
for(int i=x;i>0;i-=lowbit(i)){
ans+=tree[i];
}
return ans;
}
注意修改某个位置x的值的时候,也要修改a[x]的值
int tree[maxn],a[maxn];
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,int val,int n){
for(int i=x;i<=n;i+=lowbit(i)){
tree[i]=val;
for(int j=1;j<lowbit(i);j<<=1){
tree[i]=max(tree[i],tree[i-j]);
}
}
}
inline int query(int x,int y){
int ans=0;
while(x<=y){
ans=max(ans,a[y]);
for(y-=1;y-lowbit(y)>=x;y-=lowbit(y)){
ans=max(ans,tree[y]);
}
}
return ans;
}
思路:
若对于a数组,a[i]=1表示,数列中存在i这个值,那么如果用最直接的方式求第k小
就是从小到大遍历计数,直到找到k个数。这种方法是O(n)的时间复杂度
利用树状数组求就是二进制的思想,每次不是一个格子一个格子的求,而是多个求。
例如数列:2,3,6,8,10,求第k=4小的数
对于原始数组a 1 2 3 4 5 6 7 8 9 10
数组内值 0 1 1 0 0 1 0 1 0 1
ans为当前得到的数的值,cnt为上次得到的数所在的排名,即第cnt小的数。
t=log2(10)=3;
t=3时,ans=0+2^3=8<10,cnt+tree[8]=a[1]+a[2]+……+a[8]=4==4,所以ans-=8=0;
t=2时,ans=0+2^2=4<10,cnt+tree[4]=0+a[1]+a[2]+a[3]+a[4]=2<k,所以cnt+=2=2;
t=1时,ans=4+2^1=6<10,cnt+tree[6]=2+a[5]+a[6]=3<k,所以cnt+=1=3;
t=0时,ans=6+1=7<10,cnt+tree[7]=3+a[7]=3<k,所以cnt+=0=3;
所以第4小的值为ans+1=8.
int tree[maxn];
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,int val){ //将x处的值增加val,此处val一般为1
for(int i=x;i<maxn;i+=lowbit(i)){
tree[i]+=val;
}
}
inline int query(int x){ //求[1,x]的所有值的和
int ans=0;
for(int i=x;i>0;i-=lowbit(i)){
ans==tree[i];
}
return ans;
}
inline get_kth(int k,int n){ //求第k小的数的值,n为最大的数的值,一般比最大数的值大1
int ans=0,cnt=0; //ans为当前是哪个数,cnt为上个数排名为多少
for(int i=log2(n);i>=0;i--){
ans+=1<<i;
if(ans>=n||cnt+tree[ans]>=k) ans-=1<<i; //若当前数大于最大的数,或者排名大于k,回退
else cnt+=tree[ans];
}
return ans+1;
}
思路:
利用差分的思想,即:
a k = ∑ i = 1 k d i , 其 中 d i = a i − a i − 1 a_k=\sum_{i=1}^kd_i,其中d_i=a_i-a_{i-1} ak=i=1∑kdi,其中di=ai−ai−1
由上式可以看出某个位置的值只和起点的值大小有关,所以修改区间的时候,只需要修改端点的值即可。
int tree[maxn];
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,int val){
for(int i=x;i<maxn;i+=lowbit(i)){
tree[i]+=val;
}
}
inline int query(int x){
int ans=0;
for(int i=x;i>0;i-=lowbit(i)){
ans+=tree[i];
}
return ans;
}
int a[maxn];
int main(){
int n;scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",a+i),update(i,a[i]-a[i-1]); //初始化
update(2,10);update(8,-10); //将区间[2,7]的所有数的值增加10
printf("%d\n%,query(4)); //查询[4]处的值
}
思路:
也是利用差分的思想:
对 于 a k 我 们 知 道 a k = ∑ i = 1 k d i 则 ∑ i = 1 k a i = ∑ i = 1 k ∑ j = 1 i d j = ∑ i = 1 k ( k + 1 − i ) ⋅ d i 则 可 得 ∑ i = 1 k a i = ∑ i = 1 k ( k + 1 ) ⋅ d i − ∑ i = 1 k i ⋅ d i = ( k + 1 ) ⋅ ∑ i = 1 k d i − ∑ i = 1 k i ⋅ d i 对于a_k我们知道a_k=\sum_{i=1}^kd_i\\ 则\sum_{i=1}^ka_i=\sum_{i=1}^k\sum_{j=1}^id_j=\sum_{i=1}^k(k+1-i)\cdot d_i\\ 则可得\sum_{i=1}^ka_i=\sum_{i=1}^k(k+1)\cdot d_i-\sum_{i=1}^ki\cdot d_i=(k+1)\cdot \sum_{i=1}^kd_i-\sum_{i=1}^ki\cdot d_i 对于ak我们知道ak=i=1∑kdi则i=1∑kai=i=1∑kj=1∑idj=i=1∑k(k+1−i)⋅di则可得i=1∑kai=i=1∑k(k+1)⋅di−i=1∑ki⋅di=(k+1)⋅i=1∑kdi−i=1∑ki⋅di
则可通过树状数组维护两个数组分别存储 d i d_i di和 i ⋅ d i i\cdot d_i i⋅di,更新区间的时候与上面一样只需要更新端点即可。
若要修改的区间为[x,y],则要更新的为update(x,val);update(y+1,-val);
若要查询的区间为[x,y],则查询的为query(y)-query(x-1);
int tree1[maxn],tree2[maxn];
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,int val){
for(int i=x;i<maxn;i+=lowbit(i)){
tree1[i]+=val;tree2[i]+=x*val;
}
}
inline int query(int x){
int ans=0;
for(int i=x;i>0;i-=lowbit(i)){
ans+=(x+1)*tree1[i]-tree2[i];
}
return ans;
}
int a[maxn];
int main(){
int n;scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",a+i),update(i,a[i]-a[i-1]); //初始化
update(2,10);update(8,-10); //将区间[2,7]所有的值增加10
printf("%d\n",query(6)-query(4-1)); //查询区间[4,6]的和
}
其他不说,简单修改就可以,对于最后的区间查询,区间修改简单推导如下:
因 为 a n m = ∑ i = 1 n ∑ j = 1 m d i j , 其 中 d i j = a i j − a ( i − 1 ) j ˙ − a i ( ˙ j − 1 ) + a ( i − 1 ) ( j − 1 ) 则 ∑ i = 1 n ∑ j = 1 m a i j = ∑ i = 1 n ∑ j = 1 m ∑ k = 1 i ∑ p = 1 j d k p = ∑ i = 1 n ∑ j = 1 m ( m ⋅ n − ( i − 1 ) ⋅ m − ( j − 1 ) ⋅ n + ( i − 1 ) ⋅ ( j − 1 ) ) ⋅ d i j 则 可 得 ∑ i = 1 n ∑ j = 1 m a i j = ( m ⋅ n ) ⋅ ∑ i = 1 n ∑ j = 1 m d i j − m ⋅ ∑ i = 1 n ∑ j = 1 m ( i − 1 ) ⋅ d i j − n ⋅ ∑ i = 1 n ∑ j = 1 m ( j − 1 ) ⋅ d i j + ∑ i = 1 n ∑ j = 1 m ( i − 1 ) ⋅ ( j − 1 ) ⋅ d i j 因为a_{nm}=\sum_{i=1}^n\sum_{j=1}^md_{ij},其中d_{ij}=a_{ij}-a_{(i-1)\dot j}-a_{i\dot (j-1)}+a_{(i-1)(j-1)} \\ 则\sum_{i=1}^n\sum_{j=1}^ma_{ij}=\sum_{i=1}^n\sum_{j=1}^m\sum_{k=1}^i\sum_{p=1}^jd_{kp}=\sum_{i=1}^n\sum_{j=1}^m(m\cdot n-(i-1)\cdot m- (j-1)\cdot n+(i-1)\cdot (j-1))\cdot d_{ij} \\则可得\sum_{i=1}^n\sum_{j=1}^ma_{ij}=\\(m\cdot n)\cdot\sum_{i=1}^n\sum_{j=1}^md_{ij}-m\cdot\sum_{i=1}^n\sum_{j=1}^m(i-1)\cdot d_{ij}-n\cdot\sum_{i=1}^n\sum_{j=1}^m(j-1)\cdot d_{ij}+\sum_{i=1}^n\sum_{j=1}^m(i-1)\cdot(j-1)\cdot d_{ij} 因为anm=i=1∑nj=1∑mdij,其中dij=aij−a(i−1)j˙−ai(˙j−1)+a(i−1)(j−1)则i=1∑nj=1∑maij=i=1∑nj=1∑mk=1∑ip=1∑jdkp=i=1∑nj=1∑m(m⋅n−(i−1)⋅m−(j−1)⋅n+(i−1)⋅(j−1))⋅dij则可得i=1∑nj=1∑maij=(m⋅n)⋅i=1∑nj=1∑mdij−m⋅i=1∑nj=1∑m(i−1)⋅dij−n⋅i=1∑nj=1∑m(j−1)⋅dij+i=1∑nj=1∑m(i−1)⋅(j−1)⋅dij
所以需要用四个数组分别维护 d i j d_{ij} dij, ( i − 1 ) ⋅ d i j (i-1)\cdot d_{ij} (i−1)⋅dij, ( j − 1 ) ⋅ d i j (j-1)\cdot d_{ij} (j−1)⋅dij和 ( i − 1 ) ⋅ ( j − 1 ) ⋅ d i j (i-1)\cdot (j-1)\cdot d_{ij} (i−1)⋅(j−1)⋅dij
若要修改的矩形区间为[x1,y1]-[x2,y2],则更新的点为
update(x1,y1,val);update(x2+1,y2+1,val);update(x1,y2+1,-val);update(x2+1,y1,-val);
若要查询的举行区间为[x1,y1]-[x2,y2],则查询的值为
query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1)
int tree1[maxn][maxn],tree2[maxn][maxn],tree3[maxn][maxn],tree4[maxn][maxn];
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,int y,int val){
for(int i=x;i<maxn;i+=lowbit(i)){
for(int j=y;j<maxn;j+=lowbit(j)){
tree1[i][j]+=val;tree2[i][j]+=(x-1)*val;
tree3[i][j]+=(y-1)*val;tree4[i][j]+=(x-1)*(y-1)*val;
}
}
}
inline int query(int x,int y){
int ans=0;
for(int i=x;i>0;i-=lowbit(i)){
for(int j=y;j>0;j-=lowbit(j)){
ans+=(x*y)*tree1[i][j]-y*tree2[i][j]-x*tree3[i][j]+tree4[i][j];
}
}
return ans;
}
int a[maxn][maxn];
int main(){
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
update(i,j,a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]);
}
}
update(2,3,10);update(7,6,10);update(2,6,-10);update(7,3,-10);
//将矩形区间[2,3]-[6,5]所有的值增加10
printf("%d\n",query(4,5)+query(2,1)-query(2,5)-query(4,2));
//查询矩形区间[3,2]-[4,5]的和
}