在这片无边无际的数字海洋中,如何从中提取出有价值的讯息,成为了计算机科学中的一项重要课题。前缀和算法,作为一种巧妙的技术,恰如其名——通过计算序列中各个元素的前缀和,能够为我们提供一种高效的查询方式,避免了重复计算的复杂度。在这篇博客中,我们将一起探索前缀和算法的奥秘,揭开它在数据处理中的应用与优势。
前缀和算法的核心思想是:***对于一个数组,预先计算出数组从第一个元素到当前元素的累积和。这种累积和的结果,能够帮助我们快速计算出数组任意区间内的和。** 举个简单的例子,假设有一个数组 A,其元素为: |
|---|
A = [a1, a2, a3, ..., an]那么,前缀和数组 S 就是一个满足如下关系的数组:
S[i] = A[1] + A[2] + ... + A[i],(1 <= i <= n)通过这个预处理步骤,我们能够将任何区间 [l, r] 内的和,通过以下公式迅速计算得到:
sum(l, r) = S[r] - S[l-1]这一公式的妙处在于,无论查询的区间多么广泛,计算时间始终是常数级别的 O(1),只需要在预处理阶段做 O(n) 的计算。
前缀和算法在多个领域都得到了广泛的应用,尤其是在处理数组、字符串等数据结构时,展现出了巨大的优势。以下是几种典型的应用场景: |
|---|
优势:
劣势:
前缀和算法的改进与扩展 在一些特殊场景下,前缀和算法也有不少改进和扩展的方案。例如: |
|---|
下面我们结合具体题目进行理解运用。
https://www.nowcoder.com/practice/acead2f4c28c401889915da98ecdc6bf?tpId=230&tqId=2021480&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196
该题是典型的IO型题目(输入输出),下面我们来分析具体输入输出要求:
示例如图:

暴力解法(会超时): 该题的思路不难,可以采取每次逐个遍历该区间累加的方式进行输出,但是如此操作,会造成极大的时间和空间冗余。
前缀和: . 先预处理出来⼀个「前缀和」数组:
#include <iostream>
using namespace std;
const int N=100010;
long long nums[N], sums[N];
int main() {
int n,p;
cin>>n>>p;//输入
for(int i=1;i<=n;i++)
{
cin>>nums[i];
}//输入各个元素
for(int i=1;i<=n;i++)
{
sums[i]=sums[i-1]+nums[i];
}//计算前缀和
while(p--)
{
int l,r;
cin>>l>>r;
cout<<sums[r]-sums[l-1]<<endl;
}
return 0;
}
// 64 位输出请用 printf("%lld")https://www.nowcoder.com/practice/99eb8040d116414ea3296467ce81cbbc?tpId=230&tqId=2023819&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196
本题为IO型,我们先来具体分析输入输出要求:
以此图为例,以 (x1, y1) 为左上角 , (x2,y2) 为右下角的子矩阵的和即为D所表示的区域。

类⽐于⼀维数组的形式,如果我们能处理出来从 [0, 0] 位置到 [i, j] 位置这⽚区域内所有 元素的累加和,就可以在 O(1) 的时间内,搞定矩阵内任意区域内所有元素的累加和。因此我们 接下来仅需完成两步即可:

这样,我们填写前缀和矩阵数组的时候,下标直接从 1 开始,能⼤胆使⽤ i - 1 , j - 1 位 置的值。 注意 dp 表与原数组 matrix 内的元素的映射关系: i. 从 dp 表到 matrix 矩阵,横纵坐标减⼀; ii. 从 matrix 矩阵到 dp 表,横纵坐标加⼀。
前缀和矩阵中 sum[i][j] 的含义,以及如何递推⼆维前缀和⽅程: a. sum[i][j] 的含义:
sum[i][j] 表⽰,从 [0, 0] 位置到 [i, j] 位置这段区域内,所有元素的累加和,即下图红黄绿蓝四个区域相加之和。 |
|---|

下面我们逐个分析这四个区域:
综上,sum[i][j]=红+绿+红+蓝+黄-红=sum[i][j-1]+sum[i-1][j]+martix[i-1][j-1]-sum[i-1][j-1]. |
|---|
接下来分析如何使⽤这个前缀和矩阵,如下图(注意这⾥的 row 和 col 都处理过了,对应的正 是 sum 矩阵中的下标):

对于左上⻆ (row1, col1) 、右下⻆ (row2, col2) 围成的区域,正好是红⾊的部分。因 此我们要求的就是红⾊部分的⾯积,继续分析⼏个区域: i. ⻩⾊,能直接求出来,就是 sum[row1 - 1, col1 - 1] (为什么减⼀?因为要剔除 掉 row 这⼀⾏和 col 这⼀列) ii. 绿⾊,直接求不好求,但是和⻩⾊拼起来,正好是 sum 表内 sum[row1 - 1][col2] 的数据; iii. 同理,蓝⾊不好求,但是 蓝 + ⻩ = sum[row2][col1 - 1] ; iv. 再看看整个⾯积,好求嘛?⾮常好求,正好是 sum[row2][col2] ; v. 那么,红⾊就 = 整个⾯积 - ⻩ - 绿 - 蓝,但是绿蓝不好求,我们可以这样减:整个⾯积 -(绿
sum[row2][col2]-sum[row2][col1 - 1]-sum[row1 - 1][col2]+sum[row1 - 1][col1 - 1] |
|---|
#include <iostream>
using namespace std;
const int N=1010;
int arr[N][N];
long long dp[N][N];//数组与前缀和数组
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>>arr[i][j];
}
}
//处理前缀和数组
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
dp[i][j]=dp[i-1][j]+dp[i][j-1]+arr[i][j]-dp[i-1][j-1];
}
}
//使用前缀和数组
while(q--)
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
cout<<dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1]<<endl;
}//输出数据
return 0;
}
// 64 位输出请用 printf("%lld")本篇关于前缀和的介绍讲解较为基础,将于后续文章中结合进阶难度的题目循序渐进,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!