一、前缀和
表示的是一个数组某一项下标之前(包括此下标的所有数组元素的和)。
根据上述表述,可以得到下面的式子:
sum[x] = a[0]+a[1]+a[2]+a[..]+a[x]
其中,a[i]表示的是原数组,sum[x]表示的是a数组前x个数的和。求这个数的目的是为了我们能够在常数时间内访问得到a数组任意区间的数字和。假设要求l到r区间的区间和,其值即为sum[r] - sum[r-1]。这些区间对应到一维的空间上即是坐标轴上一段线段。
而且,可以通过扩展扩展到二维平面撒、和三位立体空间内。
sum[x,y] = ∑∑a[i,j]
二、差分
差分的思想是利用相邻两个元素的差值的变化来表示的数组的变化,其最常用于处理m次操作之后,每一次操作给某一个区间加上或者减去一个相同的值,求所有数的值的问题。
定义:
f[i] = a[i] - a[i-1]
s[i] = ∑f[i] (从1到i)
可以得到s[i] = a[i]。 而当一个区间[l,r]加上一个相同的数字的时候,在f数组中,只有f[r]和f[r+1]发生了改变。所以在每一次遇到这样的操作的时候,只去修改f[l]和f[r+1]就可以。最后在算一次s数组就可以求出a数组的所有数字。
同样,可以将差分发展到更高维上去,常用的就是二维差分。根据二维前缀和的性质,假设一个位置(x,y),其表示的是从(x,y)这个位置一直到(0,0)这个而为之上的所有元素的和,而差分只涉及其自身和周围前面元素,并且最后是由周围的元素互相作用得到最后的元素。所以我们能够简单的推测出二维差分的公式:
p[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]
而最后的a数组推导公式为:
a[i][j] = p[i][j] + p[i-1][j] +p[i][j-1] -p[i-1][j-1]
给某一个矩阵加上一个相同的值的时候,
p[x1][y1] + = d;
p[x1][y2+1] - = d;
p[x2+1][y1] - = d;
p[x2+1][y2+1] + =d;
三.树状数组
树状数组的时间复杂度小,无论区间查询还是单点修改都是O(logn).
新建一个b数组,树状数组可以的区间查询可以查询b数组的前缀和,那么当有具体操作的时候,在[l,r]加上一个d的时候,那么利用之前的差分思想,可以进行两步操作。
1、b[l] +=d;
2、b[r+1] -=d
int a[maxn];
int lowerbit(int x){ // 求最低位的1的位置
return x & (-x);
}
void add(int x, int d){
while (x < maxn){
a[x] += d; // 两步操作
x += lowerbit(x);
}
}
int ask(int x){
int ans = 0;
while (x){
ans += a[x];
x -= lowerbit(x);
}
return ans;
}
四、RMQ问题(ST表)
预处理时间复杂度为O(nlogn),查询的时间复杂度为O(1)。
预处理:
void st_Init(){
for (int i=1; i<=n; i++) st_max[i][0] = a[i];
for (int i=1; (1<<i)<=n; i++) //步⻓
for (int j=1; j+(1<<i)-1 <= n; j++) //起始点
st_max[j][i] = max(st_max[j][i-1], st_max[j+(1<<(i-1))][i-1]);
}
查询部分:
int q_max(int l, int r){
int k = (int)(log((double)r-l+1)/log(2.0)); //求⼤于等于区间⼀半的步⻓。
return max(st_max[l][k], st_max[r - (1 << k) + 1][k]);
}
int q_min(int l, int r){
int k = (int)(log((double)r-l+1)/log(2.0));
return min(st_min[l][k], st_min[r - (1 << k) + 1][k]);
}