前缀和与差分
一、前缀和(Prefix Sum)
引入:对于一个有n个元素的数组arr,进行q次询问[L,R]的区间和。如果q较小,我们可以直接for循环一个一个元素加起来,但如果q很大,R-L也很大,那么时间复杂度就会很大。因此我们可以用前缀和进行预处理,优化时间(我以下代码里的数组均是从1开始存数据而不是0)
1.概念
用于快速计算数组中任意区间[l,r]的元素和
-
定义:设原数组为a[1..n],前缀和数组pre[0..n]满足:
pre[i]=a[1]+a[2]+...a[i];类似于高中数学里的前n项和
其中pre[0]=0(哨兵值,便于计算)
-
区间和公式:
sum(l, r) = pre[r] - pre[l - 1];
2.用途
-
多次查询区间和(如求第l到第r项的和)
-
时间复杂度:预处理O(n),单次查询O(1)
3.C代码模板
cpp
#include <stdio.h>
#define MAXN 100010
int a[MAXN], pre[MAXN];
int main() {
int n, m;
scanf("%d", &n);
pre[0]=0;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
pre[i] = pre[i - 1] + a[i]; // 构建前缀和
}
scanf("%d", &m);
while (m--) {
int l, r;
scanf("%d %d", &l, &r);
printf("%d\n", pre[r] - pre[l - 1]); // 区间和
}
return 0;
}
二、差分
引入:对于一个有n个元素的数组,对它进行m次操作,每个操作给定一个L和,然后对数组区间[L,R]下标范围内的每个元素加上一个数x,最后输出m次操作后的数组arr(n个数m次操作1次询问)每次对区间数组进行修改的操作都用了O(n)的复杂度,一共是O(m*n),为了优化时间,可以用差分
1.概念
差分是前缀和的逆运算,用于高效实现区间加法操作。
-
定义:设原数组为a[1...n],差分数组d[1...n+1]满足:
d[1]=a[1],d[i]=a[i]-a[i-1] (i≥2)
-
性质:原数组的差分数组d可通过其前缀和得到原数组
便于更好理解我举个例子
arr 1 3 7 5 2 原数组
d 1 2 4 -2 -3 原数组的差分数组d[i]=arr[i]-arr[i-1] d[1]=arr[1];
Pred 1 3 7 5 2 差分数组d的前缀和pred[i]=pred[i-1]+d[i]
还有另一种方法,不计算差分,只计算差分增量。无论原始数组值是否为0,我们都将差分数组初始化为0,d仅表示差分增量,计算d的前缀和后再加上原始数组可得到最终结果
例如:
初始arr 1 3 7 5 2 对第1~3元素+2变为 3 5 9 5 2
初始d 0 0 0 0 0
差分增量d 2 0 0 -2 0
差分增量d的前缀和 2 2 2 0 0
其前缀和加上初始arr 3 5 9 5 2 与操作后的数组arr一致
-
核心操作:对区间[l,r]所有元素加相当于:
d[l] += x; d[r + 1] -= x; // 注意 r+1 可能越界,需判断最终通过差分数组的前缀和还原操作后的数组:a[i]=d[1]+d[2]+...+d[i]=pred[i]
为什么区间[l,r]那么多个元素,却只需要对d[l]和d[r+1]操作就能得到结果?
答:d数组的前缀和是不断叠加的,d[1]++,那么pred[1]及其后面的数都会+1
2.用途
-
多次对区间进行加/减操作,最后输出整个数组
-
时间复杂度:单次修改 O(1),还原 O(n)
3.C代码模板
法一
cpp
#include <stdio.h>
#define MAXN 100010
int d[MAXN]; // 差分数组,初始为0
int main() {
int n, m;
scanf("%d %d", &n, &m);
while (m--) {
int l, r, x;
scanf("%d %d %d", &l, &r, &x);
d[l] += x;
if (r + 1 <= n) d[r + 1] -= x; // 防止越界
}
// 通过前缀和还原最终数组
for (int i = 1; i <= n; i++) {
d[i] += d[i - 1]; // 此时 d[i] 即为 a[i]
printf("%d ", d[i]);
}
return 0;
}
法二
cpp
#include <stdio.h>
#define MAXN 100010
int main(){
int n,m;
int arr[MAXN];
scanf("%d %d",&n,&m);
int d[MAXN]={0};
for(int i=1;i<=n;i++){
scanf("%d",&arr[i]);
}
while(m--){
int l,r,x;
scanf("%d %d %d",&l,&r,&x);
d[l]+=x;
if(r+1<=n){
d[r+1]-=x; //此时d[i]为差分增量,而不是差分
}
}
for(int i=1;i<=n;i++){
d[i]+=d[i-1];
printf("%d ",d[i]+arr[i]);
}
return 0
}
学习完以上内容后可以自行去搜索近五年蓝桥杯相关真题进行练习
