2.1 树状数组

树状数组,顾名思义,长得像树的数组,用于处理一些单点修改以及区间查询的问题。其时间复杂度为 O(log⁡2n)O(\log _2n)O(log2​n) 。如过我们使用一些一般的数据结构,那么一定会 TLE。这时,树状数组就可以帮助我们解决这些问题。

Tips:

树状数组和线段树具有相似的功能,但他俩毕竟还有一些区别:树状数组能有的操作,线段树一定有;线段树有的操作,树状数组不一定有。但是树状数组的代码要比线段树短,思维更清晰,速度也更快,在解决一些单点修改的问题时,树状数组是不二之选。——OI WiKi

在我正式介绍树状数组的三大基本操作之前,我们要先了解树状数组的内部结构及基本函数:

对于一个数组 A[6]={1,2,5,8,10,20}A[\ 6\ ]=\left\{1,2,5,8,10,20\right\}A[ 6 ]={1,2,5,8,10,20} ,它所生成的树状数组为 Bit[6]={1,3,5,16,10,30}Bit[\ 6\ ]=\left\{1,3,5,16,10,30\right\}Bit[ 6 ]={1,3,5,16,10,30} ,如上图所示。

那么,我们可以明显发现,对于 Bit[i]Bit[\ i\ ]Bit[ i ] 而言,它应该等于 a[i]+Bit[k](1⩽k<i)a[\ i\ ]+Bit[\ k\ ](1 \leqslant k <i )a[ i ]+Bit[ k ](1⩽k<i)。

那么,如何求得 iii 呢?

这牵扯到树状数组的一个关键函数:LowBitLowBitLowBit 函数(烦诶,还能不能进正题!)

2.1.1 LowBit 函数

理解 LowBit 的含义:

LowBit[i]LowBit[\ i\ ]LowBit[ i ] 存储着 iii 转化成二进制数之后,只保留最低位的 111 及其后面的 000 后,然后再转成十进制数后的书。(也就是树状数组中第 iii 号位的子叶个数)(当然,在实际操作中,LowBit 往往以函数的形式出现)

那么,我们一般有两种方法求 LowBit:

int LowBit_1(int num){return num&-num;
}
int LowBit_2(int num){return num-(num&(num–1));
}
//我们在实际操作中往往使用第1种

下面,我来简单证明一下为什么第 111 种方法是可行的:

假设有一整数 NNN ,若我们直接将 NNN 按位取反得到 MMM 后,会有 N&M=0N \& M=0N&M=0 。

但是,在二进制中, NNN 的相反数应该是 M+1M+1M+1 那么答案就有所不同。

在假设 NNN 的最后一位 111 出现在第 ppp 位,一共有 qqq 位,那么 NNN 的第 p+1p+1p+1 ,第 p+2p+2p+2 位一直到第 qqq 位都应该是 000 。

易得:MMM 的第 p+1p+1p+1 ,第 p+2p+2p+2 位一直到第 qqq 位都应该是 111 ,而第 ppp 位是 000 。

因为有加 111 操作,所以,第 qqq 位就变为 222 ,向前进 111 ,第 q−1q-1q−1 位就变为 222 ,向前进 111 …

如此相加,直到第 ppp 位变为 111 为止。

那么此时,MMM 的第 p+1p+1p+1 ,第 p+2p+2p+2 位一直到第 qqq 位都应该是 000 ,而第 ppp 位是 111 。

由于 N,MN,MN,M 的第 111 位到第 p−1p-1p−1 位都未改变,所以只考虑第 ppp 位到第 qqq 位。

NNN 的第 ppp 位是 111 ,而第 p+1p+1p+1 ,第 p+2p+2p+2 位一直到第 qqq 位都是 000 。

MMM 的第 ppp 位是 111 ,而第 p+1p+1p+1 ,第 p+2p+2p+2 位一直到第 qqq 位都是 000 。

很明显,最终结果只有第 ppp 位是 111 ,而其余全部是 000 .

最终结果与假设相符,命题得证

为什么我看着像伪证?

有了 LowBit ,我们就可以进行其它操作了。

2.1.2 UpDate 函数

UpDate 就是将一个新元素 NNN 塞进 BitBitBit 数组里的一个操作。

一般来讲,我们会在输入 AAA 数组时,通过 UpDate 初始化 BitBitBit 数组,也会在修改 AAA 数组里面的值时,用 UpDate 来更新 BitBitBit 数组

但是,由于树状数组的结构特殊,所以在处理时不能只修改 Bit[i]Bit[\ i\ ]Bit[ i ] 的值:

还是这个例子:假设我们要改变 A[1]A[\ 1\ ]A[ 1 ] 里的值,我们实际上要修改 Bit[1],Bit[2],Bit[4]Bit[\ 1\ ],Bit[\ 2\ ],Bit[\ 4\ ]Bit[ 1 ],Bit[ 2 ],Bit[ 4 ] 的值。

显然,对于Bit[i]Bit[\ i\ ]Bit[ i ] ,它与 Bit[i+LowBit(i)]Bit[\ i+LowBit(\ i\ )\ ]Bit[ i+LowBit( i ) ] 之间有 111 条连线,自然,处理 Bit[i]Bit[\ i\ ]Bit[ i ] 时,我们也需要将 Bit[i+LowBit(i)]Bit[\ i+LowBit(\ i\ )\ ]Bit[ i+LowBit( i ) ] 处理了。

void UpDate(int num,int sum){for(int i=num;i<=n;i+=LowBit(i)){Bit[i]+=sum;          //上文所述,与 Bit[i] 有关系的全部都要处理}
}

2.1.3 Sum 函数

在实际操作中,往往需要求得区间和或某一点的值,一般情况下,我们会使用前缀和。但是,由于我们对 AAA 数组进行了修改,使用前缀和的时间复杂度会有所提升,这时,我们可以使用 Sum 函数来解决这一问题。

Sum(i)Sum(\ i\ )Sum( i ) 用于求解 A[1]+A[2]+⋯+A[i]A[\ 1\ ]+A[\ 2\ ]+\cdots+A[\ i\ ]A[ 1 ]+A[ 2 ]+⋯+A[ i ] 的值。但是,我们可以观察下图,很明显,A[1]+A[2]+A[3]+A[4]=Bit[4]A[\ 1\ ]+A[\ 2\ ]+A[\ 3\ ]+A[\ 4\ ]=Bit[\ 4\ ]A[ 1 ]+A[ 2 ]+A[ 3 ]+A[ 4 ]=Bit[ 4 ] , A[5]+A[6]=Bit[6]A[\ 5\ ]+A[\ 6\ ]=Bit[\ 6\ ]A[ 5 ]+A[ 6 ]=Bit[ 6 ] 。而 LowBit(4)=4,LowBit(6)=2LowBit(\ 4\ )=4,LowBit(\ 6\ )=2LowBit( 4 )=4,LowBit( 6 )=2,自然我们可以通过 LowBit 以及 BitBitBit 数组来达到缩减时间复杂度的效果。

long long int Sum(int num){          //一般情况下,Sum 函数需要开 long longlong long int ans=0;for(int i=num;i>=1;i-=LowBit(i)){ans+=Bit[i];            //上文已提,可优化时间复杂度}return ans;
}

好的,三个基本函数已经具备了,我们可以欣赏三个基本操作了。

2.2 一维树状数组基本操作

接下来,我们将依次介绍单点修改,区间查询区间查询,单点修改区间修改,区间查询三个基本操作

2.2.1 单点修改,区间查询

单点修改,区间查询应该是最简单的一个操作,直接套用三个函数即可,只是在进行区间查询时需要注意采用前缀和的思想,即 Sum(right)−Sum(left−1)Sum(right)-Sum(left-1)Sum(right)−Sum(left−1)

Eg_1 【模板】树状数组 1

这道题是就是一道裸题(英语老师:说了多少次了不能裸奔!)

#include<cstdio>
long long int a[500005],Bit[500005];
int n,m,x,y,z;
int Low_Bit(int num){return num&-num;
}
void Up_Date(int num,int sum){for(int i=num;i<=n;i+=Low_Bit(i)){Bit[i]+=sum;}
}
long long int Sum(int num){long long int ans=0;for(int i=num;i>=1;i-=Low_Bit(i)){ans+=Bit[i];}return ans;
}           //基本操作。注意 long long
int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){scanf("%lld",&a[i]);Up_Date(i,a[i]);            //对 Bit 进行初始化}for(int i=1;i<=m;i++){scanf("%d%d%d",&x,&y,&z);if(x==1){           //修改操作Up_Date(y,z);}else{           //查询操作printf("%lld\n",Sum(z)-Sum(y-1));           //前缀和求解}}return 0;
}

2.2.2 区间修改,单点查询

如果抛开树状数组不看,我们一般用什么进行区间修改,单点查询呢?

是的,差分数组!

那么,我们需要简单回顾一下如何用差分数组进行区间修改,单点查询。

现有一数组 A[5]={1,2,3,4,5}A[\ 5\ ]=\{1,2,3,4,5\}A[ 5 ]={1,2,3,4,5} ,其对应的差分数组为 P[5]={1,1,1,1,1}P[\ 5\ ]=\{1,1,1,1,1\}P[ 5 ]={1,1,1,1,1} 。

现在,我们将区间 [2,4][\ 2,4\ ][ 2,4 ] 加上 111 那么我们在差分数组中,只需要 P[2]+=1,P[5]−=1P[\ 2\ ]+=1,P[\ 5\ ]-=1P[ 2 ]+=1,P[ 5 ]−=1 即可。

我们都知道,差分数组的前缀和数组是原数组,当我们要将区间 [l,r][\ l,r\ ][ l,r ] 加上 xxx 时,我们需要将 P[l]P[\ l\ ]P[ l ] 加上 xxx ,这样,我们的数组从第 lll 号元素开始都会增加 xxx ,同样,当我们将 P[r]P[\ r\ ]P[ r ] 减去 xxx 后,数组从第 rrr 号元素开始都会减去 xxx ,和前面的增加 xxx 刚好抵消,我们也i就达到了 P[l]P[\ l\ ]P[ l ] 加上 xxx 的效果。

那么,我们只需要将差分数组的操作塞进树状数组里就可以了。

Eg_2 【模板】树状数组 2

裸题梅开二度

#include<cstdio>
long long int a[1000005],Bit[1000005],p[1000005];
long long int n,m,x,y,z;
long long int lowbit(long long int num){return num&-num;
}
void update(long long int num,long long int sum){for(int i=num;i<=n;i+=lowbit(i)){Bit[i]+=sum;}
}
long long int Sum(long long int num){long long int ans=0;for(int i=num;i>=1;i-=lowbit(i)){ans+=Bit[i];}return ans;
}           //基本操作
int main(){scanf("%lld%lld",&n,&m);for(int i=1;i<=n;i++){scanf("%lld",&a[i]);p[i]=a[i]-a[i-1];          //求得初始差分数组update(i,p[i]);           //塞入树状数组}for(int i=1;i<=m;i++){scanf("%lld",&x);if(x==1){            //更改操作scanf("%lld%lld%lld",&x,&y,&z);update(x,z);         //修改 Bit[x]update(y+1,-z);             //修改 Bit[y+1]}else{            //查询操作scanf("%lld",&y);printf("%lld\n",Sum(y));         //求差分数组的前缀和即可}}return 0;
}

2.2.3 区间修改,区间查询

那么,对于区间修改这一操作,我们依旧可以使用差分数组来完成,那么,我们如何进行区间查询操作呢?

设原数组为 AAA ,差分数组为 PPP ,那么,求区间 [1,i][\ 1,i\ ][ 1,i ] 的结果为:

Sum(1,i)=∑x=1ia[x]=∑x=1i∑y=1xP[y]=∑x=1ix×P[i−x+1]=i×∑x=1iP[i]−∑x=1i(x−1)×P[x]\begin{aligned}Sum(\ 1,i\ ) &=\sum_{x=1}^ia[\ x\ ]\\& = \sum_{x=1}^i\sum_{y=1}^xP[\ y\ ]\\ & =\sum_{x=1}^ix\times P[\ i-x+1\ ]\\&=i\times \sum_{x=1}^iP[\ i\ ]-\sum_{x=1}^i(x-1)\times P[\ x\ ]\end{aligned}Sum( 1,i )​=x=1∑i​a[ x ]=x=1∑i​y=1∑x​P[ y ]=x=1∑i​x×P[ i−x+1 ]=i×x=1∑i​P[ i ]−x=1∑i​(x−1)×P[ x ]​

观察减式两边,我们发现:左边式子的基本结构为 P[i]P[\ i\ ]P[ i ] ,而右边式子的基本结构为 i×P[i+1]i\times P[\ i+1\ ]i×P[ i+1 ] 。那么,我们就可以分别用两个树状数组进行维护。

在修改时,分别对两个树状数组进行更改,查询时,通过前缀和,套用上面的公式即可。

Eg_3 区间修改,区间查询

又双叒叕是道裸题

#include<cstdio>
long long int a[1000005],Bit[1000005],Bit_1[1000005],p[1000005];
long long int n,m,x,y,z;
long long int Low_Bit(long long int num){return num&-num;
}
void Up_Date(long long int num,long long int sum){for(int i=num;i<=n;i+=Low_Bit(i)){Bit[i]+=sum;Bit_1[i]+=sum*(num-1);           //上文已提,分别维护 P[i] 和 P[i]*(i-1) 的值}
}
long long int Sum(long long int num){long long int ans=0;for(int i=num;i>=1;i-=Low_Bit(i)){ans+=Bit[i]*num-Bit_1[i];           //上文已提,套用公式即可}return ans;
}
int main(){scanf("%lld%lld",&n,&m);for(int i=1;i<=n;i++){scanf("%lld",&a[i]);p[i]=p[i-1]+a[i];Up_Date(i,a[i]-a[i-1]);          //塞入差分数组}for(int i=1;i<=m;i++){scanf("%lld",&x);if(x==1){            //修改scanf("%lld%lld%lld",&x,&y,&z);Up_Date(x,z);Up_Date(y+1,-z);         //按照差分思想进行修改}else{          //查询scanf("%lld%lld",&y,&z);printf("%lld\n",Sum(z)-Sum(y-1));           //按照前缀和思想进行求解}}return 0;
}

2.3 离散化

考虑到在用树状数组时,往往需要使用离散化,在这里简单介绍一下

离散化是程序设计中一个常用的技巧,它可以有效的降低时间复杂度。(其实就是哈希的一种)

有些数据本身很大自身无法作为数组的下标保存对应的属性。如果这时只是需要这堆数据的相对属性,那么可以对其进行离散化处理。当数据只与它们之间的相对位置有关,而与具体是多少无关时,可以进行离散化。比如当数据个数很小,数据范围却很大时(超过1e9)就考虑离散化成更小的值,能够实现更多的算法。——PPT

举个例子:我们有数组 A[5]={114514,996,857,442759,2147483647}A[\ 5\ ]=\{114514,996,857,442759,2147483647\}A[ 5 ]={114514,996,857,442759,2147483647} 。显然,如果要将 A[i]A[\ i\ ]A[ i ] 作为数组下标,显然是不行的。采用离散化,我们就可以将 AAA 数组改为 A[5]={3,2,1,4,5}A[\ 5\ ]=\{3,2,1,4,5\}A[ 5 ]={3,2,1,4,5}

废话那么多,所以如何离散化怎么写呢?

2.3.1 结构体离散化

我们在输入数据时,可以通过结构体得到对应的下标。我们通过对数据进行从小到大的排序,会得到一个相关的下表的顺序,越靠前的下标所对应的数值越小,所以,在遍历时,我们也要将一个小一点的值给它。

#include<cstdio>
#include<algorithm>
using namespace std;
struct node{int num,sum;            //sum 存数值,num 存下标
}a[1005];
bool cmp(node a,node b){return a.sum<b.sum;          //按 sum 从小到大排序
}
int main(){int n;scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i].sum);a[i].num=i;}sort(a+1,a+1+n,cmp);for(int i=1;i<=n;i++){a[a[i].num].sum=i;         //对对应的下标所对应的数值进行更改}for(int i=1;i<=n;i++){printf("%d ",a[i].sum);}return 0;
}

运行结果如下:

2.3.2 STL+二分离散化(这个常用)

对于上面的离散化方法,有一点美中不足的地方:

如上图所示,有时候,我们希望它最后得到的结果是 1,1,2,3,31,1,2,3,31,1,2,3,3 ,怎么办呢?

那么,我们就需要两个函数: sort 和 lower_bound 函数

sort 函数大家都很熟悉,lower_bound 函数则用于查找有序序列中第一个大于或等于所要查找元素的位置

那么,我们就可以得到 STL 离散化的 0.1 版本

#include<cstdio>
#include<algorithm>
using namespace std;
int a[1005],b[1005];
int main(){int n;scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);b[i]=a[i];          //复制一份原数组}sort(b+1,b+1+n);           //排序,因为 lower_bound 必须在有序数列下进行for(int i=1;i<=n;i++){a[i]=lower_bound(b+1,b+1+n,a[i])-b;           //查找该元素第一次出现的下标,赋值给原数组}for(int i=1;i<=n;i++){printf("%d ",a[i]);}return 0;
}

程序结果:

很好,我们离想要的结果又近了一步。

观察样例,我们发现:对于数组中重复的元素,我们依旧给了它一个坑位占,所以接下来我们需要去重

#include<cstdio>
#include<algorithm>
using namespace std;
int a[1005],b[1005];
int main(){int n;scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);b[i]=a[i];}sort(b+1,b+1+n);int len=unique(b+1,b+1+n)-b-1;            //unique 是一个去重函数,同样要在排序之后才能使用for(int i=1;i<=n;i++){a[i]=lower_bound(b+1,b+1+len,a[i])-b;          //由于去重后,数组大小减小了,需要注意}for(int i=1;i<=n;i++){printf("%d ",a[i]);}return 0;
}

程序结果:

那么,我们就完成了离散化

2.4 一维树状数组运用

Eg_4 逆序对

序列中每个数字不超过 10910^9109

自然,我们需要一个离散化

我们会得到一个离散化后的数组 BBB ,对于一个 B[i]B[\ i\ ]B[ i ] 我们可以将 Bit[B[1]]Bit[\ B[\ 1\ ]\ ]Bit[ B[ 1 ] ] 及其之后有关的元素全部加一,这时,因为下标比 iii 小的数全部塞进了树状数组了,所以,Sum(B[i])Sum(\ B[\ i\ ] \ )Sum( B[ i ] ) 表示第一号元素到第 iii 号与第 iii 号元素所组成的数对中非逆序对的数量

显然,因为凡是比 B[i]B[\ i\ ]B[ i ] 小的下标又比 iii 小的都已经塞入了树状数组中,只需用 Sum 函数过一遍即可。

显然,有 iii 表示第一号元素到第 iii 号与第 iii 号元素所组成的数对的数量,用总数对数量减去非逆序对对数的数量就有了逆序对的数量

#include<cstdio>
#include<algorithm>
using namespace std;
long long int a[500005],b[500005];
long long int Bit[500005];
long long int n,ans;
long long int Low_Bit(long long int num){return num&-num;
}
void Up_Date(long long int num,long long int sum){for(int i=num;i<=n;i+=Low_Bit(i)){Bit[i]+=sum;}
}
long long int Sum(long long int num){long long int ans=0;for(int i=num;i>=1;i-=Low_Bit(i)){ans+=Bit[i];}return ans;
}           //基本操作
int main(){scanf("%lld",&n);for(int i=1;i<=n;i++){scanf("%lld",&a[i]);b[i]=a[i];}sort(b+1,b+1+n);for(int i=1;i<=n;i++){a[i]=lower_bound(b+1,b+1+n,a[i])-b;}           //离散化,这里去重可有可无for(int i=1;i<=n;i++){Up_Date(a[i],1);ans+=i-Sum(a[i]);           //上文已提具体含义}printf("%lld",ans);            //As we all know:不开 long long 见祖宗return 0;
}

2.5 二维树状数组

二维树状数组和一维树状数组一样,同样有单点修改,区间查询区间查询,单点修改区间修改,区间查询三个基本操作

2.5.1 单点修改,区间查询

二维树状数组的单点修改,区间查询和一维树状数组的单点修改,区间查询思想一致。

如果你忘了二维数组的前缀和…

Practice [HNOI2003]激光炸弹

总而言之,我们可以简单的概括一下:

若 PPP 数组代表二维数组 AAA 的前缀和,则有 P[i][j]=P[i−1][j]+P[i][j−1]−P[i−1][j−1]+A[i][j]P[\ i\ ][\ j\ ]=P[\ i-1\ ][\ j\ ]+P[\ i\ ][\ j-1\ ]-P[\ i-1\ ][\ j-1\ ]+A[\ i\ ][\ j\ ]P[ i ][ j ]=P[ i−1 ][ j ]+P[ i ][ j−1 ]−P[ i−1 ][ j−1 ]+A[ i ][ j ]

如果要求左上角为 (x1,y1)(x_1,y_1)(x1​,y1​) ,右下角为 (x2,y2)(x_2,y_2)(x2​,y2​) 的矩阵的元素和,我们计算 P[x2][y2]+P[x1−1][y1−1]−P[x1−1][y2]−P[x2][y1−1]P[\ x_2\ ][\ y_2\ ]+P[\ x_1-1\ ][\ y_1-1\ ]-P[\ x_1-1\ ][\ y_2\ ]-P[\ x_2\ ][\ y_1-1\ ]P[ x2​ ][ y2​ ]+P[ x1​−1 ][ y1​−1 ]−P[ x1​−1 ][ y2​ ]−P[ x2​ ][ y1​−1 ] 即可。

那么,我们只需要将前缀和塞入树状数组里即可

Eg_4 二维树状数组 1:单点修改,区间查询

裸题一个,代码奉上:

#include<cstdio>
int Bit[5005][5005];
int n,m,x,y,z,x1,y1,x2,y2;
int Low_Bit(int num){return num&-num;
}
void Up_Date(int x,int y,int sum){for(int i=x;i<=n;i+=Low_Bit(i)){for(int j=y;j<=m;j+=Low_Bit(j)){Bit[i][j]+=sum;}}
}
int Sum(int x,int y){int ans=0;for(int i=x;i>=1;i-=Low_Bit(i)){for(int j=y;j>=1;j-=Low_Bit(j)){ans+=Bit[i][j];}}return ans;
}           //因为是二维树状数组,所以 Up_Date 函数和 Sum 函数要改为二重循环
int main(){scanf("%d%d",&n,&m);while(scanf("%d",&x)!=EOF){if(x==1){          //修改操作scanf("%d%d%d",&x,&y,&z);Up_Date(x,y,z);    }else{scanf("%d%d%d%d",&x1,&y1,&x2,&y2);printf("%d\n",Sum(x2,y2)+Sum(x1-1,y1-1)-Sum(x2,y1-1)-Sum(x1-1,y2));            //上文已提,前缀和思想}}return 0;
}

2.5.2 区间修改,单点查询

跟一维树状数组一样,我们依旧要采用差分思想。

请看下面神奇的图片

设左上角的下标为 [x1,y1][\ x_1,y_1\ ][ x1​,y1​ ] ,右下角的下标为 [x2,y2][\ x_2,y_2\ ][ x2​,y2​ ]

我们要使红色区域的元素都加上 aaa ,那么,我们可以依葫芦画瓢的将红色区域的左上角 A[x1][y1]A[\ x_1\ ][\ y_1\ ]A[ x1​ ][ y1​ ] 增加 aaa ,同样,为了可以抵消左上角所增加的 aaa ,我们需要在 A[x1][y2+1]A[\ x_1\ ][\ y_2+1\ ]A[ x1​ ][ y2​+1 ] 以及 A[x2+1][y1]A[\ x_2+1\ ][\ y_1\ ]A[ x2​+1 ][ y1​ ] 减去一个 aaa

考虑蓝色矩阵,我们多抵消了一个 aaa ,所以,我们在 A[x2+1][y2+1]A[\ x_2+1\ ][\ y_2+1\ ]A[ x2​+1 ][ y2​+1 ] 再加上 aaa ,就完成了对二维数组的差分。

老觉得怪怪的,又说不出来……

那么,我们只需要往树状数组里塞入差分操作即可。

Eg_5 二维树状数组 2:区间修改,单点查询

今天的裸题真多啊

#include<cstdio>
long long int a[10005],Bit[10005][10005];
long long int Low_Bit(long long int num){return num&-num;
}
long long int n,m;
void Up_Date(long long int x,long long int y,long long int sum){for(int i=x;i<=n;i+=Low_Bit(i)){for(int j=y;j<=m;j+=Low_Bit(j)){Bit[i][j]+=sum;}}
}
long long int Sum(long long int x,long long int y){long long int ans=0;for(int i=x;i>=1;i-=Low_Bit(i)){for(int j=y;j>=1;j-=Low_Bit(j)){ans+=Bit[i][j];}}return ans;
}
long long int x,y,z,x1,y1,x2,y2,sum;
int main(){scanf("%lld%lld",&n,&m);while(scanf("%lld",&x)!=EOF){if(x==1){scanf("%lld%lld%lld%lld%lld",&x1,&y1,&x2,&y2,&sum);Up_Date(x1,y1,sum);Up_Date(x2+1,y2+1,sum);Up_Date(x1,y2+1,-sum);Up_Date(x2+1,y1,-sum);}else{scanf("%lld%lld",&x2,&y2);printf("%lld\n",Sum(x2,y2)+Sum(0,0)-Sum(x2,0)-Sum(0,y2));           //前缀和思想}}return 0;
}

2.5.3 区间修改,区间查询

与一维相类似,主要还是推公式

我们假设 aaa 数组为原数组, ppp 数组为差分数组

Sum(x,y)=∑i=1x∑j=1ya[i][j]=∑i=1x∑j=1y∑k=1i∑h=1jp[k][h]=∑i=1x∑j=1yp[i][j]×(x+1−i)×(y+1−j)=(x+1)×(y+1)×∑i=1x∑j=1yp[i][j]+∑i=1x∑j=1yp[i][j]×i×j−(x+1)×∑i=1x∑j=1yp[i][j]×j−(y+1)×∑i=1x∑j=1yp[i][j]×i\begin{aligned}Sum(\ x,y\ )&=\sum_{i=1}^x\sum_{j=1}^ya[\ i\ ][\ j\ ]\\&=\sum_{i=1}^x\sum_{j=1}^y\sum_{k=1}^i\sum_{h=1}^jp[\ k\ ][\ h\ ]\\&=\sum_{i=1}^x\sum_{j=1}^yp[\ i\ ][\ j\ ]\times(x+1-i)\times(y+1-j)\\&=(x+1)\times(y+1)\times\sum_{i=1}^x\sum_{j=1}^yp[\ i\ ][\ j\ ]+\sum_{i=1}^x\sum_{j=1}^yp[\ i\ ][\ j\ ]\times i\times j-(x+1)\times\sum_{i=1}^x\sum_{j=1}^yp[\ i\ ][\ j\ ]\times j-(y+1)\times\sum_{i=1}^x\sum_{j=1}^yp[\ i\ ][\ j\ ]\times i\end{aligned}Sum( x,y )​=i=1∑x​j=1∑y​a[ i ][ j ]=i=1∑x​j=1∑y​k=1∑i​h=1∑j​p[ k ][ h ]=i=1∑x​j=1∑y​p[ i ][ j ]×(x+1−i)×(y+1−j)=(x+1)×(y+1)×i=1∑x​j=1∑y​p[ i ][ j ]+i=1∑x​j=1∑y​p[ i ][ j ]×i×j−(x+1)×i=1∑x​j=1∑y​p[ i ][ j ]×j−(y+1)×i=1∑x​j=1∑y​p[ i ][ j ]×i​

LaTeX\LaTeXLATE​X 打得我心累

那么,我们就需要 444 个树状数组,分别维护 p[i][j],p[i][j]×x,p[i][j]×y,p[i][j]×x×yp[\ i\ ][\ j\ ],p[\ i\ ][\ j\ ]\times x,p[\ i\ ][\ j\ ]\times y,p[\ i\ ][\ j\ ]\times x\times yp[ i ][ j ],p[ i ][ j ]×x,p[ i ][ j ]×y,p[ i ][ j ]×x×y

Eg_6 二维树状数组 3:区间修改,区间查询

这应该是最后一道裸题了

#include<cstdio>
int read(){int a=0,b=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-'){b=-1;}ch=getchar();}while(ch>='0'&&ch<='9'){a=a*10+ch-'0';ch=getchar();}return a*b;
}           //快读
int x1,y1,x2,y2,sum;
int n,m;
long long int Bit[2100][2100],Bit1[2100][2100],Bit2[2100][2100],Bit3[2100][2100];
int Low_Bit(int num){return num&-num;
}
void Up_Date(int x,int y,int sum){for(int i=x;i<=n;i+=Low_Bit(i)){for(int j=y;j<=m;j+=Low_Bit(j)){Bit[i][j]+=sum;Bit1[i][j]+=sum*x;Bit2[i][j]+=sum*y;Bit3[i][j]+=sum*x*y;}            //上文已提,分别对4个值进行维护}
}
long long int Sum(int x,int y){long long int ans=0;for(int i=x;i>=1;i-=Low_Bit(i)){for(int j=y;j>=1;j-=Low_Bit(j)){ans+=Bit[i][j]*(x+1)*(y+1)+Bit3[i][j]-Bit1[i][j]*(y+1)-Bit2[i][j]*(x+1);}            //上文已提,套公式}return ans;
}
int main(){n=read(),m=read();while(scanf("%d",&sum)!=EOF){if(sum==1){x1=read(),y1=read(),x2=read(),y2=read(),sum=read();Up_Date(x1,y1,sum);Up_Date(x2+1,y2+1,sum);Up_Date(x1,y2+1,-sum);Up_Date(x2+1,y1,-sum);          //差分思想进行维护}else{x1=read(),y1=read(),x2=read(),y2=read();printf("%lld\n",Sum(x2,y2)+Sum(x1-1,y1-1)-Sum(x2,y1-1)-Sum(x1-1,y2));//上文已提,前缀和思想}}return 0;
}

简单の暑假总结——树状数组相关推荐

  1. 洛谷 P5057 [CQOI2006]简单题(树状数组)

    嗯... 题目链接:https://www.luogu.org/problem/P5057 首先发现这道题中只有0和1,所以肯定与二进制有关.然后发现这道题需要支持区间更改和单点查询操作,所以首先想到 ...

  2. NEFU大一暑假集训-树状数组

    题集链接 目录: OP A Ultra-QuickSort 题目大意 思路 代码 B Stars 题目大意 思路 代码 C Mobile phones 题目大意 思路 代码 D Cows 题目大意 思 ...

  3. CF1621G Weighted Increasing Subsequences(离散化+树状数组优化dp+栈维护后缀最大值+计数)

    problem luogu-link solution 显然单独考虑每个 iii 的贡献,即被多少个合法上升子序列包含. 令 x=max⁡{j∣j>i∧aj>ai}x=\max\{j\ | ...

  4. 二维树状数组(bzoj 1452: [JSOI2009]Count)

    1452: [JSOI2009]Count Time Limit: 10 Sec  Memory Limit: 64 MB Submit: 2496  Solved: 1451 [Submit][St ...

  5. 树状数组区间修改和区间求和

    最一般树状数组能做到的操作是单点修改,区间求和,都是log(n)级别的.原理就是用树状数组维护a[i]的部分和. 想要做到修改区间,求单点值也很简单,用树状数组维护a[i]的差分数组d[i]的部分和既 ...

  6. 小朋友排队(树状数组)

    问题描述 n 个小朋友站成一排.现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友. 每个小朋友都有一个不高兴的程度.开始的时候,所有小朋友的不高兴程度都是0. 如果某个小朋友 ...

  7. 树状数组 讲解和题目集

    树状数组 树状数组作为一种实现简单.应用较广的高级数据结构,在OI界的地位越来越重要,下面我来简单介绍一下树状数组和它的简单应用. 一.树状数组简介 树状数组:顾名思义,是一种数组,其中包含了树的思想 ...

  8. hdu 1754 I Hate It(线段树/树状数组)

    I Hate It Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total ...

  9. CF56E Domino Principle 树状数组 + 简单dp

    一个比较简单的题,但是我还是没做出来(哭. 很容易想到从后往前做,所以我们可以维护一个dp数组f,f(i)表示到第i个牌倒下能达到的最远距离. f直接倒着跑,每次取[x,x+h−1][x,x+h-1] ...

最新文章

  1. MySQL学习(四)查询
  2. docker 镜像_Docker镜像分层
  3. Avenger v1.0.6.0
  4. php抓取统计局区划代码,抓取国家统计局的代码和名称,爬取,区域,划分,及
  5. qt创建图形项,添加自定义窗口
  6. 69讲入门python_Python入门,一定要吃透这69个内置函数
  7. python-面向对向-实例方法的继承-多继承父类中的super与子类实例对象的关系
  8. eclipse 下编写java code 比较好的设置和快捷键
  9. 设计模式学习笔记之二:观察者模式
  10. 获取TextView每行的内容 (涉及getLineCount为0,getLineEnd问题)
  11. 【java笔记】成员内部类 局部内部类 匿名内部类
  12. Redis的高级应用-安全性和主从复制
  13. 维基百科英文语料库下载地址
  14. 六度空间理论(数据结构图,c语言版)
  15. android 如何读取u盘中数据恢复,U盘数据恢复
  16. 读书笔记----《平凡的世界》第四篇
  17. 故宫元宵节首开夜场,票务系统HTTPS加密护航
  18. 管理学之父彼得·德鲁克:目标管理的基本思路、原则及分解方法
  19. 考试系统mysql数据库设计_驾校理论考试系统之数据库设计一
  20. SSL、WSS和HTTPS之间的关系

热门文章

  1. 计算机浏览器存储技术cookie、sessionStorage、localStorage
  2. ThinkPad告别蓝快,自己使用VHD安 WIN8.1并成功激活
  3. 无人机遥感技术在各个行业中的应用
  4. 渴望学习 Shader的伙伴有福了!高斯模糊详解!
  5. Jquery通过方向键(左上右下) 来控制表格内输入框的自由移动
  6. 计算机视觉六大技术:图像分类、目标检测、目标跟踪、语义分割、实例分割、影像重建..
  7. 微信小程序学习日记5
  8. assignment to ‘ *‘ form incompatible pointer type ‘LinkStack‘{aka ‘ *‘}的问题解决
  9. 计算机的假桌面,原来大神的电脑桌面是这样子的!网友:感觉自己用了25年的假电脑...
  10. python 海康工业相机二次开发 参数获取