数组小技巧 区间操作

数组小技巧 区间操作

猛然发现自己的个人主页被访问了1,000次以上了!

嘛嘛,那么讲一些自己的关于数组的心得体会吧。正好也是整理一下。


声明:

查询单个元素:给定一个下标,获取数组上对应下标上元素的值

修改单个元素:给定一个下标,让对应下标上的元素加上一个给定的值

查询区间和:对于一个给定的合法区间[i,j]来说,查询数组在[i,j]上的所有元素之和

修改区间和:对于一个给定的合法区间[i,j]来说,让[i,j]里的每一个元素都加上某一个给定的值

单个元素虽然可以看成对于某个区间[i,i]上的操作,但是我还是要特意拿出来。

数组下标从1开始


普通数组:

const int maxm=100;
int arr[maxm];

查询单个元素时间复杂度: O(1)

修改单个元素时间复杂度: O(1)

查询区间和时间复杂度: O(n)

修改区间和时间复杂度: O(n)

评价:被广泛使用的结构......


前缀和:

int sum[maxm];
for(int i=1;i<maxm;++i)sum[i]=sum[i-1]+arr[i];


即对于前缀和数组sum来说

sum[i]=arr[1]+arr[2]+......+arr[i]

查询单个元素时间复杂度: O(1) -> sum[i]-sum[i-1]

修改单个元素时间复杂度: O(n)

查询区间和时间复杂度: O(1) -> sum[j]-sum[i-1]

修改区间和时间复杂度: O(n)

评价:在数组不会发生变化,且需要频繁查询区间和的时候的很好的选择。


差分:

int diff[maxm]
diff[1]=arr[1];
for(int i=2;i<maxm;++i)diff[i]=arr[i]-arr[i-1];


即对于差分数组diff来说:

diff[1]=arr[1], diff[i]=arr[i]-arr[i-1];

查询单个元素时间复杂度: O(n) -> diff[i]+diff[i-1]+.... + diff[1]

修改单个元素时间复杂度: O(1) -> 只考虑涉及到arr[i]的元素即可

查询区间和时间复杂度: O(n)

修改区间和时间复杂度: O(1) -> 考虑[i,j]区间+1,则diff[i]+=1,diff[j+1]-=1即可

评价:好像用得不是很多...用在需要对区间进行更新而且中间还不能查询的时候。


三者关系:

普通数组求前缀和变为前缀和数组

差分数组求前缀和变为普通数组

前缀和数组差分变为普通数组

普通数组差分变为差分数组


引入数据结构:树状数组,此处不对树状数组原理做任何解释

//定义操作lowerbit
//bit->binary index tree
inline int lowerbit(int x){return x&-x;}
//定义插入操作
int _ins(int i,int x){while(i<maxm)bit[i]+=x,i+=lowerbit(i);}
//定义查询操作
int query(int i){
    int ans=0;
    while(i)ans+=bit[i],i-=lowerbit(i);
    return ans;
}
for(int i=1;i<maxm;++i)
_ins(i,arr[i]);

以上是最基本的树状数组代码,即对原arr数组建立一个树状数组

查询单个元素时间复杂度: O(lgn)

修改单个元素时间复杂度: O(lgn)

查询区间和时间复杂度: O(lgn) -> query(j)-query(i-1)

修改区间和时间复杂度: O(nlgn)


让我们考虑对差分数组diff建立树状数组:

for(int i=1;i<maxm;++i)
_ins(i,diff[i]);

这样做完之后我们就能得到:

查询单个元素时间复杂度: O(lgn)

修改单个元素时间复杂度: O(lgn)

查询区间和时间复杂度: O(nlgn)

修改区间和时间复杂度: O(lgn) -> 考虑区间[i,j]+1,_ins(i,1), _ins(j+1,-1)


问题来了,树状数组和对diff建立的树状数组都不能很好的进行区间修改和查询......那么我们有没有什么办法呢

考虑 arr[1]+arr[2]+....+arr[n]

用diff改写一下成为

(diff[1]) + (diff[1] + diff[2]) + ..... + (diff[1] + diff[2] + ..... + diff[n])

合并一下同类项

n*diff[1] + (n-1)*diff[2] + ...... + 1*diff[n] ---- (1)

改写一下式子

n*(diff[1] + diff[2] + ...... + diff[n]) - ( 0*diff[1] + 1*diff[2] + ...... + (n-1)*diff[n]) ---- (2)

至此,我们便得到了可以进行区间修改的差分树状数组了。

我们我们不仅对差分数组diff建立一个树状数组快速求diff上的区间和,同时对 (i-1)*diff[i] 也建立一个树状数组快速求该数组的区间和,再相减即可。

//(i-1)*diff[i]数组的ins和query
//[i,j]+val时
_ins(i,(i-1)*val);
_ins(j+1,-(n-j-1)*val);
//查询[i,j]时
query(j)-query(i-1);

至此,我们通过建立两个树状数组即可得到区间修改和区间查询的良好数据结构了。

查询单个元素时间复杂度: O(lgn)

修改单个元素时间复杂度: O(lgn)

查询区间和时间复杂度: O(lgn)

修改区间和时间复杂度: O(lgn)

评价:常数也线段树要小,支持区间修改的结构。可是我没怎么用过,一般都是写打lazy tag的线段树了。


思考题:

在上一段中,我们是对(2)式建立了两个树状数组来完成区间操作,那么,我们能不能仅仅对(1)式建立一个树状数组来完成区间操作,而且还要少一倍的空间呢?

文章被以下专栏收录