Smile, breathe and go slowly.
引入问题:
已知一个数组 a[ 10 ] ,初始值全部为1。 如果要将范围 [ 1, 5 ]之间的每一个数字都加1,应当如何操作。
最简单直接的操作就是for循环了:
for(int i=1;i<=5;i++) a[i]++;
但是如果数据范围较大,以及操作次数比较多,用for循环时间复杂度较高。
先看看什么是差分数组:
简单来说,前一个元素减去后一个元素形成的数组就是差分数组。
d[ 0 ] = a[ 0 ] ( i = 0 )
d[ i ] = a[ i ] - a[ i - 1 ] ( i > 0)
举个例子:
数组 a:
1,2,3,4,5
差分数组 d 应当是:
1,1,1,1,1 //第一个元素没有可以减的元素,所以就等于它本身
//即规定d[ 0 ] = a[ 0 ]
我们注意到:
d[ 1 ] = a[ 1 ] - a[ 0 ]
d[ 2 ] = a[ 2 ] - a[ 1 ]
d[ 3 ] = a[ 3 ] - a[ 2 ]
d[ 4 ] = a[ 4 ] - a[ 3 ]
即:
a[ 0 ] = d[ 0 ]
a[ 1 ] = d[ 1 ] + d[ 0 ]
a[ 2 ] = d[ 2 ] + d[ 1 ] + a[ 0 ]
…
可推导出 a[ i ] = d[ i ] + d[ i - 1 ] + d[ i - 2 ] + …+d[ 0 ]
所以差分数组到底有什么用呢?
回到引入问题:
要使[ 1, 5 ] 之间的每个元素都+1,用差分数组只需要这样写:
d[ 1 ]++;
d[ 5+1 ]--;
因为d[ 1 ] = a[ 1 ] - a[ 0 ],a[ 1 ]增大了,a[ 0 ]不变,所以d[ 1 ]应当加上相应的变化量。
同理,中间的元素是同步增大的,所以无需处理。
对于d [ 6 ] = a[ 6 ] - a[ 5 ],a[ 5 ]增大了,a[ 6 ]不变,所以d[ 6 ]应当减去相应的变化量。
原本复杂的for循环,现在只需两行代码。
差分数组不仅仅是一个优秀的数据结构,还是一种很好的思想。
修改区间的时间复杂度是O(1),查询点的时间复杂度为O(n)
对应练习题:
基础题: CF44C Holidays(差分数组)
进阶题:P4939 Agent2(树状数组+差分数组)。
基础题代码 (Python版):
n, m = input().split()
n, m=int(n), int(m)
out=False
d=[0 for i in range(0,n+2)] #d为差分数组
for i in range(0,m):
a, b=input().split()
a, b=int(a), int(b)
d[a]+=1 #差分数组的更新
d[b+1]-=1
for i in range(1,n+1):
count=sum(d[0:i+1]) #求第i天浇水的次数
if count!=1: #浇水次数不等于1就输出,结束
print(i,count)
out=True
break
if not out: #没有问题就输出OK
print("OK")
进阶题代码(C++版):
(这题要用树状数组来维护更新,关于树状数组,大家可以自己在网上查阅相关资料,或者在评论区讨论)
#include<iostream>
using namespace std;
int d[10000002];
int n, m;
int lowbit(int x) {
return x & -x;
}
int getSum(int a) {
int sum = 0;
for (int i = a; i >= 1; i -= lowbit(i)) {
sum += d[i];
}
return sum;
}
void update(int a, int b) {
for (int i = a; i <= n; i += lowbit(i)) {
d[i]++;
}
for (int i = b+1 ; i <= n; i += lowbit(i)) {
d[i]--;
}
}
int main() {
ios::sync_with_stdio(false);
cin >> n >> m;
int a, b, c;
for (int i = 1; i <= m; i++) {
cin >> c;
if (c) {
cin >> a;
cout << getSum(a) << endl;
}
else{
cin >> a >> b;
update(a, b);
}
}
return 0;
}