前缀和与差分(免费)(一维+二维,超详细)

一.一维前缀和

一、核心概念‌

定义‌
前缀和是一种预处理技术,通过构建数组prefix[],其中prefix[i]表示原数组arr前i个元素的和。例如:

原数组:[1, 3, 5, 7, 9]

前缀和数组:[1, 4, 9, 16, 25](prefix[2] = 1+3 = 4)

数学表达‌

递推公式:prefix[i] = prefix[i-1] + arr[i-1](下标从1开始)

区间和计算:sum[l, r] = prefix[r] - prefix[l-1]

二、实现步骤‌

预处理前缀和数组
int prefix[n+1];
for (int i = 1; i <= n; i++) {
    prefix[i] = prefix[i-1] + arr[i-1];
}


时间复杂度:O(n)
空间复杂度:O(n)


查询区间和

int query(int l, int r) {
    return prefix[r] - prefix[l-1];
}

时间复杂度:O(1)

三、应用场景‌

  • 高频区间求和‌:如统计数组任意子数组的和,避免重复计算。
  • 差分数组结合‌:通过前缀和还原差分操作后的数组,用于区间批量增减。
  • 滑动窗口优化‌:快速计算窗口内元素和,例如求长度为k的子数组最大和。

四、边界处理技巧‌


下标从1开始‌

定义prefix[0]=0,避免l=0时的越界问题。

负数与溢出‌

使用long long存储前缀和防止溢出。

五、经典例题‌

基础应用‌

题目:给定数组和多个查询[l, r],输出每个查询的区间和。

解法:直接套用前缀和模板。

扩展问题‌

统计满足条件的子数组数量(如和为k的倍数)。

六、对比暴力法

方法预处理时间查询时间适用场景
暴力遍历O(n)单次查询
前缀和O(n)O(1)高频区间查询

代码

#include<bits/stdc++.h> //万能头文件
using namespace std;
int main(){
    int n,p; //数组长度为n,询问p次
    cin>>n>>p;
    int a[n+1];
    int sum[n+1]={0};
    for(int i=1;i<=n;i++){
        cin>>a[i];
        sum[i]=sum[i-1]+a[i]; //预处理
    }
    while(p--){ //询问p次
        int l;
        int r;
        cin>>l>>r;
        cout<<sum[r]-sum[l-1]<<endl; //a[l]至a[r]的和
    }
    return 0;
}

小结

通过预处理将区间查询优化为常数时间,前缀和是处理线性数据求和问题的核心工具。

二.一维差分


一维差分是处理数组区间修改的高效算法,核心思想是通过维护差分数组将区间操作优化为单点操作。


一、基本定义‌

差分数组构造‌

给定原数组 a[1..n],其差分数组 d[] 定义为:

d[1] = a[1](假设 a[0]=0)

d[i] = a[i] - a[i-1](i ≥ 2)

此时,原数组 a[i] 是 d[] 的前缀和:

a[i] = d[1] + d[2] + ... + d[i]

差分与前缀和的互逆关系‌

差分是前缀和的逆运算:对差分数组求前缀和可还原原数组。


二、核心操作:区间修改‌

若需将原数组区间 [L, R] 的所有元素值增加 c,只需修改差分数组的两个端点:

d[L] += c

d[R+1] -= c(若 R+1 > n 则忽略)

原理‌:

d[L] += c 使得 a[L] 及后续所有元素前缀和增加 c;

d[R+1] -= c 抵消了 a[R+1] 及后续元素的额外增量。

时间复杂度‌:区间修改仅需 ‌O(1)‌。

操作示例‌

假设原数组 a = [3, 5, 2, 7],差分数组 d = [3, 2, -3, 5]。

将区间 [1, 3](下标从1开始)加 4:
d[1] += 4 → d = [7, 2, -3, 5]

d[4] -= 4 → d = [7, 2, -3, 1]

还原原数组(前缀和):

a[1]=7, a[2]=7+2=9, a[3]=9+(-3)=6, a[4]=6+1=7 → 结果 [7, 9, 6, 7]

三、实现步骤‌

初始化差分数组

// 原数组 a[], 差分数组 d[]
d[1] = a[1];
for (int i = 2; i <= n; i++) {
    d[i] = a[i] - a[i-1];
}

执行区间修改

void add(int L, int R, int c) {
    d[L] += c;
    if (R + 1 <= n) d[R+1] -= c; // 边界检查
}

还原原数组(前缀和)

for (int i = 2; i <= n; i++) {
    d[i] += d[i-1]; // d[i] 变为修改后的 a[i]
}
// 此时 d[] 即为更新后的原数组

四、应用场景

高效处理区间更新‌:如批量增减、多次区间操作(如 m 次操作总复杂度 ‌O(m + n)‌)。
单点查询辅助‌:修改后通过前缀和快速获取任意位置值。

五、常见问题‌


下标起点‌:通常从 ‌1 开始‌,避免边界特判(d[0] 无意义)。

越界处理‌:若 R+1 > n,跳过 d[R+1] 的修改。


代码

#include<bits/stdc++.h> //万能头文件
using namespace std;
int main(){
    int n,p; //数组长度为n,询问p次
    cin>>n>>p;
    int a[n+1]={0};
    int dif[n+1]={0};
    for(int i=1;i<=n;i++){
        cin>>a[i];
        dif[i]=a[i]-a[i-1]; //预处理
    }
    while(p--){
        int l;
        int r;
        int k;
        cin>>l>>r>>k;
        dif[l]+=k; ////a[l]与a[l-1]的差发生改变
        if(r!=n)dif[r+1]-=k; //特判:最后一个会超界,否则会RE
    }
  //还原数组:差分数组的前缀和数组是原数组
  int now=0;
    for(int i=1;i<=n;i++){
        now+=dif[i];
        cout<<now<<" ";
    }
    return 0;
}


小结

一维差分的核心价值是将 ‌O(n) 的区间操作降为 O(1)‌,特别适合大规模数据批量更新场景。



三 . 二维前缀和


二维前缀和是一种用于快速计算矩阵子区域和的预处理技术,广泛应用于图像处理、动态规划等领域。

基本概念

二维前缀和是基于一维前缀和的扩展,它预先计算并存储从矩阵左上角(0,0)或(1,1)到任意点(i,j)形成的矩形区域内所有元素的和。

计算方法

前缀和数组构建

假设原始矩阵为mat[m][n],前缀和矩阵pre[m+1][n+1]的计算公式为:

pre[i][j] = mat[i][j] + pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1]
解释:
  • mat[i][j]是当前元素
  • pre[i-1][j]是上方矩形区域的和
  • pre[i][j-1]是左侧矩形区域的和
  • pre[i-1][j-1]是被重复计算的部分,需要减去

子矩阵和查询

给定子矩阵的左上角坐标(x1,y1)和右下角坐标(x2,y2),其和计算公式为:

sum = pre[x2][y2] - pre[x1-1][y2] - pre[x2][y1-1] + pre[x1-1][y1-1]
解释:

pre[x2][y2]是整个大矩形区域的和

减去上方不需要的区域pre[x1-1][y2]

减去左侧不需要的区域pre[x2][y1-1]

加上被重复减去的左上角区域pre[x1-1][y1-1]

代码实现
 

#include <iostream>
using namespace std;

const int N = 1005;
int mat[N][N], pre[N][N];

int main() {
    int m, n;
    cin >> m >> n;
    
    // 输入矩阵(1-based索引)
    for(int i = 1; i <= m; i++) {
        for(int j = 1; j <= n; j++) {
            cin >> mat[i][j];
        }
    }
    
    // 构建前缀和数组
    for(int i = 1; i <= m; i++) {
        for(int j = 1; j <= n; j++) {
            pre[i][j] = mat[i][j] + pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1];
        }
    }
    
    // 查询示例
    int q, x1, y1, x2, y2;
    cin >> q;
    while(q--) {
        cin >> x1 >> y1 >> x2 >> y2;
        int sum = pre[x2][y2] - pre[x1-1][y2] - pre[x2][y1-1] + pre[x1-1][y1-1];
        cout << sum << endl;
    }
    
    return 0;
}


这段代码首先构建二维前缀和数组,然后可以快速查询任意子矩阵的和

应用场景

  • 图像处理‌:快速计算图像局部区域像素值总和
  • 动态规划‌:优化某些需要频繁计算子矩阵和的DP问题
  • 游戏开发‌:计算地图区域内的资源总量
  • 数据分析‌:快速统计二维数据表的区域汇总

时间复杂度分析

‌预处理‌:O(mn),只需遍历一次原始矩阵

‌查询‌:O(1),只需常数次运算即可得到结果

相比暴力计算的O(mn)查询时间,二维前缀和在多次查询场景下优势明显

注意事项

  • 通常使用1-based索引更方便处理边界情况
  • 前缀和数组大小应比原始矩阵多一行一列
  • 注意整数溢出问题,特别是元素值较大时

差分是前缀和的逆运算,两者常结合使用。


四 . 二维差分

一、核心原理‌

1. ‌差分数组定义‌

设原矩阵为 A[i][j],其对应的二维差分矩阵 D[i][j] 满足:

A[i][j] = D[1][1] 到 D[i][j] 的前缀和‌

差分数组通过以下公式计算(边界外元素为0):

‌D[i][j] = A[i][j] - A[i-1][j] - A[i][j-1] + A[i-1][j-1]‌

此公式表示当前元素减去左方和上方元素,再加回被重复减去的左上角元素。

2. ‌子矩阵修改操作‌

对原矩阵的子矩阵 (x1,y1)(左上角)到 (x2,y2)(右下角)的所有元素 ‌增加 c‌ ,只需修改差分矩阵的四个点:

D[x1][y1]+=c     // 影响右下所有区域
D[x1][y2+1]−=c  // 抵消右侧多余区域
D[x2+1][y1]−=c  // 抵消下方多余区域
D[x2+1][y2+1]+=c // 补偿重复抵消区域


原理‌:通过四个位置的增减,使修改仅作用于目标子矩阵(容斥原理)

二、实现步骤‌

1. ‌差分矩阵构建‌
  • 方法一:直接按定义计算(推荐):
for (int i = 1; i <= n; i++)
  for (int j = 1; j <= m; j++)
    D[i][j] = A[i][j] - A[i-1][j] - A[i][j-1] + A[i-1][j-1];
  • 方法二:插入法(初始化全0,逐步插入元素):
// 假设初始差分矩阵全0
void insert(int x1, int y1, int x2, int y2, int c) {
    D[x1][y1] += c;
    D[x1][y2+1] -= c;
    D[x2+1][y1] -= c;
    D[x2+1][y2+1] += c;
}
// 遍历原矩阵,插入每个元素
for (int i = 1; i <= n; i++)
  for (int j = 1; j <= m; j++)
    insert(i, j, i, j, A[i][j]); // 单点插入

2. ‌修改子矩阵
void add_submatrix(int x1, int y1, int x2, int y2, int c) {
    D[x1][y1] += c;
    D[x1][y2+1] -= c;    // 超出右侧边界
    D[x2+1][y1] -= c;    // 超出下侧边界
    D[x2+1][y2+1] += c;  // 补偿重叠部分
}
3. ‌还原原矩阵‌

对差分矩阵求二维前缀和即可得到更新后的 A:

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        A[i][j] = D[i][j] + A[i-1][j] + A[i][j-1] - A[i-1][j-1];
    }
}

三、应用场景‌

  • 动态区域更新‌

多次修改矩阵的子矩阵(如游戏地图资源刷新、图像滤镜处理)。

  • 结合前缀和

先差分更新,再前缀和查询,实现高效“区间修改+区间查询”。

  • 矩阵压缩‌

与一维差分嵌套使用,处理高维数据。

四、复杂度分析‌

  • 构建差分矩阵‌:O(nm)
  • 子矩阵修改‌:O(1)(仅修改4个点)
  • 还原原矩阵‌:O(nm)(一次二维前缀和计算)

相比暴力更新(每次修改 O(nm)),在多次操作时优势显著

五、完整代码示例

#include <iostream>
using namespace std;
const int N = 1010;

int A[N][N], D[N][N]; // 原矩阵和差分矩阵

// 差分修改操作
void insert(int x1, int y1, int x2, int y2, int c) {
    D[x1][y1] += c;
    D[x1][y2+1] -= c;
    D[x2+1][y1] -= c;
    D[x2+1][y2+1] += c;
}

int main() {
    int n, m, q;
    cin >> n >> m >> q;

    // 输入原矩阵 + 构建差分矩阵
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            cin >> A[i][j];
            insert(i, j, i, j, A[i][j]); // 单点插入初始化
        }

    // 执行q次区域修改
    while (q--) {
        int x1, y1, x2, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        insert(x1, y1, x2, y2, c); // 修改子矩阵
    }

    // 通过前缀和还原更新后的矩阵
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            A[i][j] = D[i][j] + A[i-1][j] + A[i][j-1] - A[i-1][j-1];
            cout << A[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

六、注意事项‌

  • 索引处理‌

推荐使用 ‌1-based索引‌(数组从 [1][1] 开始),避免边界特判。

  • 数组大小‌

差分矩阵需比原矩阵多一行一列(如 D[n+1][m+1]),防止越界。

  • 溢出问题‌

大数值多次操作时需用 long long 防止溢出。

  • 与一维差分关系‌

二维差分可视为一维差分的嵌套扩展,核心思想均为‌用单点操作表示区间修改‌。


总结

前缀和和差分是处理数据区间操作的核心技术,两者互逆,常结合使用以高效解决‌区间查询‌和‌区间修改‌问题。

### 一维前缀和差分 #### 计算方法 在一维情况下,给定一个长度为 \( n \) 的数组 `arr`,其对应的前缀和数组 `prefix_sum` 定义如下: \[ prefix\_sum[i] = arr[0] + arr[1] + ... + arr[i],\quad i=0,1,...,n-1 \] 通过构建这个前缀和数组,可以在常数时间内查询任意区间 `[l,r]` 上元素之和。 ```python def build_prefix_sum_1d(arr): n = len(arr) prefix_sum = [0] * (n + 1) for i in range(1, n + 1): prefix_sum[i] = prefix_sum[i - 1] + arr[i - 1] return prefix_sum def query_range_sum(prefix_sum, l, r): return prefix_sum[r + 1] - prefix_sum[l] # Example usage: array = [3, 1, 5, 2, 7, 4] ps = build_prefix_sum_1d(array) print(query_range_sum(ps, 1, 3)) # Output should be 8 which is sum of elements from index 1 to 3. ``` 对于一维差分,则定义了一个新的数组 `diff` 来表示相邻两个位置的变化量。当需要频繁更新某个区间的值时,仅需修改该区间的起点和终点处的数值即可实现高效的操作[^2]. --- ### 二维前缀和差分 #### 计算方法 扩展到二维空间中,假设有一个大小为 \( m×n \) 的矩阵 `matrix`,那么它的前缀和可以通过下面的方式计算得到: \[ preSum(i,j)=preSum(i−1,j)+preSum(i,j−1)−preSum(i−1,j−1)+matrix(i,j),\quad i>0,\ j>0 \] 这里需要注意的是边界条件处理;而对于整个区域内的求和则可通过四个角点来快速得出结果。 ```python def build_prefix_sum_2d(matrix): rows = len(matrix) cols = len(matrix[0]) ps = [[0]*(cols+1) for _ in range(rows+1)] for row in range(1,rows+1): for col in range(1,cols+1): ps[row][col]=ps[row-1][col]+ps[row][col-1]-ps[row-1][col-1]+matrix[row-1][col-1] return ps def get_region_sum(ps,top_left,bottom_right): top,left=top_left bottom,right=bottom_right total_area=ps[bottom+1][right+1] remove_top=ps[top][right+1] remove_left=ps[bottom+1][left] add_overlap=ps[top][left] return total_area-remove_top-remove_left+add_overlap example_matrix=[ [6 ,5 ,-4], [-2,-1, 9 ], [7 ,8 ,-3 ] ] ps_example=build_prefix_sum_2d(example_matrix) result=get_region_sum(ps_example,(0,0),(1,1)) print(result)# Should output the sum within specified region according to example matrix and coordinates given above. ``` 同样,在二维场景下也可以利用差分的思想来进行高效的批量加减法运算,特别是在面对矩形区域内增加相同值的需求时非常有用[^3]. ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值