定义

前缀和(Prefix Sum)是一种重要的预处理技术,用于快速计算数组某个区间内元素的和。给定数组 ,前缀和数组 定义为:

表示原数组 从下标 0 到下标 (含)的所有元素之和。

有了前缀和数组,任意区间 (含)的和可以 计算:

如果 ,则 1

一维前缀和

代码实现

#include <bits/stdc++.h>
using namespace std;
 
// 构建前缀和数组
vector<long long> buildPrefixSum(const vector<int>& arr) {
    int n = arr.size();
    vector<long long> prefix(n);
    prefix[0] = arr[0];
    for (int i = 1; i < n; i++) {
        prefix[i] = prefix[i - 1] + arr[i];
    }
    return prefix;
}
 
// 查询区间 [l, r] 的和(包含端点)
long long rangeSum(const vector<long long>& prefix, int l, int r) {
    if (l == 0) return prefix[r];
    return prefix[r] - prefix[l - 1];
}
 
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    vector<int> arr = {1, 3, 5, 7, 9, 11};
    auto prefix = buildPrefixSum(arr);
    
    cout << rangeSum(prefix, 1, 3) << endl;  // 3 + 5 + 7 = 15
    cout << rangeSum(prefix, 0, 5) << endl;  // 1 + 3 + 5 + 7 + 9 + 11 = 36
    cout << rangeSum(prefix, 2, 2) << endl;  // 5
    
    return 0;
}

模板

#include <bits/stdc++.h>
using namespace std;
 
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    vector<long long> a(n), prefix(n);
    
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        prefix[i] = a[i] + (i ? prefix[i-1] : 0);
    }
    
    // 查询区间 [l, r]
    int l, r;
    cin >> l >> r;
    long long ans = prefix[r] - (l ? prefix[l-1] : 0);
    cout << ans << endl;
    
    return 0;
}

二维前缀和

二维前缀和用于快速计算矩阵中某个子矩阵的元素之和。给定矩阵 ,二维前缀和 表示以 为左上角、 为右下角的子矩阵元素之和:

利用二维前缀和,可以 计算任意子矩阵 的和:

代码实现

#include <bits/stdc++.h>
using namespace std;
 
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int m = 4, n = 5;
    vector<vector<int>> a(m, vector<int>(n));
    vector<vector<long long>> prefix(m, vector<long long>(n));
    
    // 读取矩阵(示例)
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            a[i][j] = i * n + j + 1;  // 示例值
        }
    }
    
    // 构建二维前缀和
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            prefix[i][j] = a[i][j];
            if (i > 0) prefix[i][j] += prefix[i-1][j];
            if (j > 0) prefix[i][j] += prefix[i][j-1];
            if (i > 0 && j > 0) prefix[i][j] -= prefix[i-1][j-1];
        }
    }
    
    // 查询子矩阵和
    auto query = [&](int x1, int y1, int x2, int y2) -> long long {
        long long res = prefix[x2][y2];
        if (x1 > 0) res -= prefix[x1-1][y2];
        if (y1 > 0) res -= prefix[x2][y1-1];
        if (x1 > 0 && y1 > 0) res += prefix[x1-1][y1-1];
        return res;
    };
    
    cout << query(1, 1, 2, 3) << endl;  // 示例
    
    return 0;
}

差分数组

差分是前缀和的逆运算。当我们需要对数组的某个区间进行批量加/减操作时,差分数组可以让我们 完成更新,最后再用前缀和还原。

给定原数组 ,差分数组 定义为:

对区间 所有元素加

// 差分数组的更新:O(1)
void rangeAdd(vector<long long>& diff, int l, int r, int v) {
    diff[l] += v;
    if (r + 1 < diff.size()) {
        diff[r + 1] -= v;
    }
}
 
// 还原前缀和得到原数组
void restore(vector<long long>& diff) {
    for (int i = 1; i < diff.size(); i++) {
        diff[i] += diff[i - 1];
    }
}

完整示例

#include <bits/stdc++.h>
using namespace std;
 
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n = 10;
    vector<long long> diff(n + 1, 0);  // 差分数组,多开一个位置
    
    // 模拟对数组执行以下操作:
    // [1, 4] 范围加 3
    // [3, 7] 范围加 2
    // [5, 9] 范围减 1
    
    auto rangeAdd = [&](int l, int r, int v) {
        diff[l] += v;
        diff[r + 1] -= v;
    };
    
    rangeAdd(1, 4, 3);  // 位置 1-4 加 3
    rangeAdd(3, 7, 2);  // 位置 3-7 加 2
    rangeAdd(5, 9, -1); // 位置 5-9 减 1
    
    // 还原原数组
    vector<long long> arr(n);
    arr[0] = diff[0];
    for (int i = 1; i < n; i++) {
        arr[i] = arr[i-1] + diff[i];
    }
    
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    
    return 0;
}

二维差分

二维差分用于对子矩阵进行批量加法操作:

void matrixAdd(vector<vector<long long>>& diff, 
                int x1, int y1, int x2, int y2, long long v) {
    diff[x1][y1] += v;
    diff[x1][y2 + 1] -= v;
    diff[x2 + 1][y1] -= v;
    diff[x2 + 1][y2 + 1] += v;
}

典型应用场景

子数组和为 K 的个数

#include <bits/stdc++.h>
using namespace std;
 
int subarraySum(vector<int>& nums, int k) {
    unordered_map<long long, int> mp;
    mp[0] = 1;
    
    long long prefix = 0;
    int count = 0;
    
    for (int num : nums) {
        prefix += num;
        if (mp.find(prefix - k) != mp.end()) {
            count += mp[prefix - k];
        }
        mp[prefix]++;
    }
    
    return count;
}

连续子数组最大和(Kadane 算法)

#include <bits/stdc++.h>
using namespace std;
 
long long maxSubArray(vector<int>& nums) {
    long long maxSum = nums[0];
    long long curSum = nums[0];
    
    for (int i = 1; i < nums.size(); i++) {
        curSum = max((long long)nums[i], curSum + nums[i]);
        maxSum = max(maxSum, curSum);
    }
    
    return maxSum;
}

矩阵区域和检索

class NumMatrix {
private:
    vector<vector<int>> prefix;
public:
    NumMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        prefix.resize(m + 1, vector<int>(n + 1, 0));
        
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                prefix[i][j] = matrix[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1];
            }
        }
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        return prefix[row2+1][col2+1] - prefix[row1][col2+1] - prefix[row2+1][col1] + prefix[row1][col1];
    }
};

参考资料

Footnotes

  1. 前缀和是一种空间换时间的策略,通过 的预处理实现 的区间查询。