关于前缀和

前缀和完全指南

  • 算法

  • 前缀和

  • 差分

  • 算法入门

  • 数据结构 categories:

  • 算法入门


前缀和是算法入门最基础的技巧之一,它可以将区间求和的时间复杂度从 O (n) 优化到 O (1),是解决大量数组、矩阵问题的核心工具。本文从一维到二维,从基础到进阶,带你彻底搞懂前缀和的所有用法,包含完整的 C++ 代码实现和经典例题,完全适配算法竞赛风格,看完就能上手。


一、什么是前缀和?

前缀和(Prefix Sum),顾名思义,就是数组的前 i 个元素的累加和。

它的核心作用是:预处理之后,O (1) 时间查询任意区间的和

如果没有前缀和,你每次查询区间[l,r]的和,都要遍历从 l 到 r 的所有元素,时间复杂度 O (n),如果有 q 次查询,总时间就是 O (nq),数据量大的时候会非常慢。

而有了前缀和,我们只需要 O (n) 的时间预处理,之后每次查询都是 O (1),总时间 O (n+q),效率提升了好几个量级。


一维前缀和的核心公式

对于原数组 a[1..n],我们定义前缀和数组 s[0..n],其中: s[i] = a[1] + a[2] + ... + a[i] 也就是前 i 个元素的累加和,其中 s[0] = 0,用来处理边界情况。

那么,任意区间 [l, r] 的和,就可以表示为:

sum(l, r) = s[r] - s[l-1]

这个公式是前缀和的核心,非常好理解:前 r 个元素的和,减去前 l-1 个元素的和,剩下的就是 l 到 r 的和。


举个例子

比如我们有原数组:

|--------|---|---|---|---|---|
| 下标 i | 1 | 2 | 3 | 4 | 5 |
| a[i] | 1 | 2 | 3 | 4 | 5 |

那么我们的前缀和数组 s 就是:

|--------|---|---|---|---|----|----|
| 下标 i | 0 | 1 | 2 | 3 | 4 | 5 |
| s[i] | 0 | 1 | 3 | 6 | 10 | 15 |

现在我们要查询区间 [2,4] 的和,也就是 2+3+4=9,用公式计算: s[4] - s[1] = 10 - 1 = 9,完全正确!


二、一维前缀和的实现

一维前缀和的实现非常简单,只需要遍历一遍数组,累加计算前缀和即可,下面是符合算法竞赛风格的完整代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
const int maxn=1e5+10; 
ll n,a[maxn],s[maxn]; 
void sol(){
    cin>>n; 
    for(ll i=1;i<=n;i++)cin>>a[i];//预处理前缀和 
    for(ll i=1;i<=n;i++)s[i]=s[i-1]+a[i];
    ll q;
    cin>>q; 
    while(q--){ 
    ll l,r;
    cin>>l>>r;     
    cout<<s[r]-s[l-1]<<endl; 
    } 
} 
int main(){
sol();
return 0;
}

注意:这里我们用了1-based 下标,也就是数组从 1 开始,这样可以避免 l=1 的时候 l-1=-1 的越界问题,s [0] 默认是 0,刚好处理边界,这是前缀和的标准写法,强烈推荐。


三、一维前缀和的经典应用

除了最基础的区间求和,一维前缀和还有很多常用的应用场景:

  1. 子数组和问题:比如求有多少个子数组的和等于 K,这就是 LeetCode 560 题,用前缀和 + 哈希表可以 O (n) 解决。

  2. 前缀和优化 DP:很多 DP 的状态转移里,有区间求和的操作,用前缀和可以把转移的时间从 O (n) 优化到 O (1)。

  3. 差分的逆运算:差分最后要通过前缀和得到最终的数组,这个我们后面会详细讲。


四、二维前缀和:矩阵的区间求和

一维的前缀和解决了数组的区间求和,那如果是矩阵,我们要查询一个子矩阵的和,怎么办?这时候就需要二维前缀和


二维前缀和的核心公式

对于原矩阵 a[1..n][1..m],我们定义前缀和矩阵 s[0..n][0..m],其中 s[i][j] 表示从左上角 (1,1) 到右下角 (i,j) 的所有元素的和。

预处理公式:

s[i][j] = a[i][j] + s[i-1][j] + s[i][j-1] - s[i-1][j-1]

这个公式用到了容斥原理,我们来解释一下:

  • s[i-1][j]:当前位置上面的矩形的和

  • s[i][j-1]:当前位置左边的矩形的和

  • 这两个加起来,s[i-1][j-1] 被加了两次,所以要减去一次

  • 最后加上当前的 a [i][j],就是当前的 s [i][j]

查询公式

如果我们要查询左上角 (x1,y1),右下角 (x2,y2) 的子矩阵的和,公式是:

sum = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]

同样是容斥原理:

  • 整个大矩形的和 s [x2][y2]

  • 减去上面的部分 s [x1-1][y2]

  • 减去左边的部分 s [x2][y1-1]

  • 这时候,左上角的 s [x1-1][y1-1] 被减了两次,所以要加回来一次


举个例子

比如我们有 3x3 的原矩阵:

|---|---|---|---|
| | 1 | 2 | 3 |
| 1 | 1 | 2 | 3 |
| 2 | 4 | 5 | 6 |
| 3 | 7 | 8 | 9 |

预处理后的前缀和矩阵 s:

|---|---|----|----|----|
| | 0 | 1 | 2 | 3 |
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 3 | 6 |
| 2 | 0 | 5 | 12 | 21 |
| 3 | 0 | 12 | 27 | 45 |

现在我们要查询子矩阵 (2,2) 到 (3,3) 的和,也就是 5+6+8+9=28,用公式计算: s[3][3] - s[1][3] - s[3][1] + s[1][1] = 45 -6 -12 +1 =28,完全正确!


五、二维前缀和的实现

二维前缀和的实现也很简单,按行遍历预处理即可,完整代码如下:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1010;
ll n,m,q,a[maxn][maxn],s[maxn][maxn];
void sol(){
    cin>>n>>m>>q;
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=m;j++){
            s[i][j]=a[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];
        }
    }
    while(q--){
        ll x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        cout<<s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]<<endl;
    }
}
int main(){
    sol();
    return 0;
}

六、进阶:差分,前缀和的逆运算

差分(Difference Array)是前缀和的逆运算,它可以把区间更新的操作,从 O (n) 优化到 O (1),最后只需要求一次前缀和,就能得到最终的数组,是区间更新的神器。


一维差分

定义差分数组 d,其中 d [i] = a [i] - a [i-1],也就是原数组的差分。

如果我们要对区间 [l,r] 的所有元素都加 v,那么只需要两步操作:

d[l] += v; d[r+1] -= v;

然后,最后对 d 求前缀和,就得到了更新后的 a 数组!

举个例子: 原数组 a 是[1,2,3,4,5],差分数组 d 是[0,1,1,1,1,1]。 现在我们要对区间 [2,4] 加 2,也就是 a [2],a [3],a [4] 都加 2,那么我们只需要: d[2] +=2d[5] -=2,d 变成[0,1,3,1,1,-1]。 然后对 d 求前缀和,得到 a 变成[1,4,5,6,5],刚好是 2,3,4 都加了 2,完全正确!


二维差分

同理,二维的差分,就是对一个子矩阵的所有元素加 v,只需要 O (1) 操作,然后求前缀和得到结果。

公式是:

d[x1][y1] += v; d[x1][y2+1] -= v; d[x2+1][y1] -= v; d[x2+1][y2+1] += v;

然后最后对 d 求二维前缀和,就得到了更新后的矩阵。


七、经典例题

下面是几个前缀和的经典例题,你可以用来练习:

  1. LeetCode 560. 和为 K 的子数组 给你一个整数数组 nums 和一个整数 k,统计数组中和为 k 的连续子数组的个数。 解法:用前缀和 + 哈希表,遍历的时候记录前缀和出现的次数,O (n) 解决。

  2. LeetCode 304. 二维区域和检索 - 矩阵不可变 给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2)。 解法:就是二维前缀和的标准应用,预处理之后 O (1) 查询。

  3. LeetCode 1109. 航班预订统计 这里有 n 个航班,它们分别从 1 到 n 进行编号。有一份预订列表 bookings,其中第 i 条记录 bookings [i] = [firsti, lasti, seatsi],意味着在 firsti 到 lasti 之间的每个航班都预订了 seatsi 个座位。请你返回一个长度为 n 的数组 answer,其中 answer [i] 是第 i 个航班的座位总数。 解法:一维差分的标准应用,区间更新,最后求前缀和。


八、前缀和的注意事项

用前缀和的时候,有几个常见的坑要注意:

  1. 下标从 1 开始:强烈推荐用 1-based 下标,避免 l=1 的时候 l-1=-1 的越界问题,s [0] 默认是 0,刚好处理边界,这是最常用的写法。

  2. 数据溢出:前缀和是累加的,很容易超过 int 的范围,所以一定要用 long long,不然会溢出,导致答案错误,这是很多新手常犯的错。

  3. 预处理顺序:二维前缀和的预处理,要按从小到大的顺序遍历,保证计算 s [i][j] 的时候,s [i-1][j]、s [i][j-1]、s [i-1][j-1] 都已经算好了。

  4. 容斥的符号:二维前缀和的公式里,最后那个加和减不要搞反,很多人会把加写成减,导致答案错误,一定要记清楚:减两个,加一个。


九、总结

前缀和是算法里最基础、最常用的技巧之一,它的思想非常简单,但是应用非常广泛,不管是入门的算法题,还是高级的算法,比如树状数组、线段树,其实都是前缀和的扩展。

学会了前缀和,你就已经解决了一大半的区间求和、区间更新的问题,这是算法入门的必经之路。


如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,我会持续更新算法入门的系列文章~

相关推荐
杨连江1 小时前
载流子矩阵限域束缚实现常温常压超导的理论与结构设计
算法
做cv的小昊1 小时前
【TJU】研究生应用统计学课程笔记(6)——第二章 参数估计(2.4 区间估计)
人工智能·笔记·线性代数·算法·机器学习·数学建模·概率论
普贤莲花2 小时前
【2026年第18周---写于20260501】---舍得
程序人生·算法·leetcode
2zcode2 小时前
基于深度学习的口腔疾病图像识别系统(UI界面+改进算法+数据集+训练代码)
人工智能·深度学习·算法
Sarvartha2 小时前
N 个字符串最长公共子序列(LCS)求解问题
数据结构·算法
一切皆是因缘际会2 小时前
下一代 AI 架构:基于记忆演化与单向投影的安全智能系统
大数据·人工智能·深度学习·算法·安全·架构
falldeep2 小时前
五分钟了解OpenClaw底层架构
人工智能·算法·机器学习·架构
m0_629494732 小时前
LeetCode 热题 100-----16.除了自身以外数组的乘积
数据结构·算法·leetcode
weixin_446260852 小时前
模型能力深度对决:GPT-4o、Claude 3.5和DeepSeek V系列模型的横向评测与未来趋势洞察
人工智能·算法·机器学习