树状数组再进阶(区间修改+区间查询)

来源:互联网 发布:双开软件哪个好 编辑:程序博客网 时间:2024/06/05 19:28

今天,我们再在树状数组上进一步突破,我们来讲一讲区间修改和区间查询。同样的,这需要各位对树状数组的基本知识有所了解,大家可以看看我的另一篇文章:树状数组趣解。
下面进入正题。
同样的,我还是先给代码,再讲解。其实,我个人比较喜欢直接看代码,有时,看别人的解释,反而看得稀里糊涂,不如先看看代码,自己会有自己的理解,然后再看解释就不会晕了。(也有可能是某些作者说得太简略了)

代码

有些是以前的代码,但由于会用到,这里可能会再写一遍。

1、lowbit

int lowbit(int x){    return x&(-x);}

2、单点修改

void add(int *C,int x,int p){    while(x<=n){        C[x]+=p;        x+=lowbit(x);    }}

3、前x项之和

int sum(int *C,int x){    int ans=0;    while(x>0){        ans+=C[x];        x-=lowbit(x);    }    return ans;}

4、求真正的前x项和(???)

int truesum(int x){    int sum1=x*sum(c1,x),sum2=sum(c2,x);    return sum1-sum2;}

5、区间查询

int query(int l,int r){    return truesum(r)-truesum(l-1);}

6、区间修改

void change(int l,int r,int p){//将[l,r]上每个数加上p    add(c1,l,p);    add(c1,r+1,-p);    add(c2,l,(l-1)*p);    add(c2,r+1,r*(-p));}

在这些代码中,有些变量可能出现的比较突兀,我会在解释中说的。

解释

好,下面是cgg解密时刻!
首先,我先讲一下总体的思路。
我们设A[]是原数组。
接下来我们需要引出一个概念:差分数组。你可能听过,也可能没听过,这都无所谓,我简单介绍一下:
我们设差分数组是C[],那么有如下定义:

  1. C[1]=A[1]
  2. C[i]=A[i]-A[i-1]

很好理解吧!
那么我们下面要做一个公式推导。
sum(1,n)
=a[1]+a[2]+a[3]+…+a[n-1]+a[n]
=c[1]+(c[1]+c[2])+…+(c[1]+c[2]+…+c[n])
=n(c[1]+c[2]+…+c[n])-(0c[1]+1c[2]+2c[3]+…+(n-1)c[n]).
这里sum表示前n项之和。
那么我们有什么发现?
我们可以设置两个树状数组,一个是c1[]表示差分数组,另一个c2[]则是c2[i]=(i-1)*c1[i]。这样的话,我们就可以区间查询了。
那么我们又是怎么实现区间修改的呢?
其实也很简单,我们看一下change()函数,有什么发现?
我们会发现,它对于两个数组,每个只改了头尾两个值,为什么?
这就要回到我们的定义,我们的两个树状数组均可以算的上是差分数组(c2也有差分的意味),所以,试想一下,如果我们对区间[l,r]上的每个数都加上p,那么c1[l+1]到c1[r]会发生变化吗?不会!因为区间内的每个数都加了p,所以他们之间的差是不变的,所以我们只需要改变两头的差值即可,这样就是实现了区间修改。
再稍微辨析一下3和4两段代码,两个都是前几项和,但3求的是树状数组前几项和,而4才是原数组的前几项和,也是我们要求的。

结束语

这样的话,我们的树状数组估计一时半会不会再进阶了,希望各位能多温习,回头我会给大家找几道习题练手。
剧透:接下来可能要讲线段树!

原创粉丝点击