前缀和和差分数组

前缀和

学习参考视频:STUACM-算法入门-前缀和与差分(含二维)_哔哩哔哩_bilibili

一维前缀和

前缀和:给定数组 a r r [ 0.. N ] arr[0..N] arr[0..N] s u m [ i ] sum[i] sum[i]表示 a r r [ 0... i ] arr[0...i] arr[0...i]区间和。

{ s u m [ 0 ] = a r r [ 0 ] , i = 0 s u m [ i ] = s u m [ i − 1 ] + a r r [ i ] , i > 0 \begin{cases} sum[0]=arr[0] &,i=0 \\ sum[i] = sum[i-1]+arr[i]&,i>0 \\ \end{cases} {sum[0]=arr[0]sum[i]=sum[i1]+arr[i],i=0,i>0

实际应用——区间和:sum(L,R) 表示 a r r [ L . . . R ] arr[L...R] arr[L...R]的区间和。暴力计算时间复杂度为 O ( N ) O(N) O(N)
s u m ( L , R ) = { s u m [ R ] , L = 0 s u m [ R ] − s u m [ L − 1 ] , L > 0 sum(L,R) = \begin{cases} sum[R]&,L=0 \\ sum[R]-sum[L-1] &,L>0\\ \end{cases} sum(L,R)={sum[R]sum[R]sum[L1],L=0,L>0

一维差分

区间加法: add(L,R,v): a r r [ L . . . R ] arr[L...R] arr[L...R]的每个数加上v

差分数组: d d d a c c acc acc的差分数组,且 d d d的前缀和 s u m d sum_d sumd即为原数组 a c c acc acc

d [ i ] = { a r r [ i ] − a r r [ i − 1 ] , i > 0 a r r [ i ] , i = 0 d[i] = \begin{cases} arr[i] - arr[i-1] &,i>0 \\ arr[i] &,i=0\\ \end{cases} d[i]={arr[i]arr[i1]arr[i],i>0,i=0

实际应用——累计区间加法:区间操作add(L,R,v) 等价于 d [ L ] + v , d [ R + 1 ] = v d[L]+v, d[R+1]=v d[L]+v,d[R+1]=v

  • 原因分析

    • d[L]+v,是指arr[L…N]都加上v

    • d[R+1]-v,是指arr[R+1…N]都减去v,以抵消d[L]+v对[R+1…N]的影响

  • 复杂度分析
    数组长度N,M次操作,则为 O ( M + N ) O(M+N) O(M+N)

  • 应用场景:

    • 多次操作一次询问:差分数组

    • 一次操作一次询问:线段树

一般情况下,不会基于arr记录结果差分,而是基于全0数组记录操作差分,通过前缀和得到总的操作 Δ d \Delta d Δd,原数组最后的结果为 a r r + Δ d arr + \Delta d arr+Δd

二维前缀和

给定矩阵 M [ n × m ] M[n\times m] M[n×m]

矩阵和:sum(a,b,c,d),(a,b)到(c,d)的和,即以(a,b)和(c,d)为对角顶点的矩阵和,暴力计算时间复杂度为 O ( n m ) O(nm) O(nm)

定义 s u m [ i ] [ j ] sum[i][j] sum[i][j] 是从(0,0)到(i,j)的和
{ s u m [ 0 ] [ j ] = s u m [ 0 ] [ j − 1 ] + M [ 0 ] [ j ] , i = 0 , j ≠ 0 s u m [ i ] [ 0 ] = s u m [ i − 1 ] [ 0 ] + M [ i ] [ 0 ] , i ≠ 0 , j = 0 s u m [ i ] [ j ] = M [ i ] [ j ] + s u m [ i − 1 ] [ j ] + s u m [ i ] [ j − 1 ] − s u m [ i − 1 ] [ j − 1 ] , o t h e r \begin{cases} sum[0][j] = sum[0][j-1]+M[0][j]&,i=0,j\neq 0\\ sum[i][0] = sum[i-1][0]+M[i][0]&,i\neq0,j= 0\\ sum[i][j] = M[i][j] + sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1]&,other \\ \end{cases} sum[0][j]=sum[0][j1]+M[0][j]sum[i][0]=sum[i1][0]+M[i][0]sum[i][j]=M[i][j]+sum[i1][j]+sum[i][j1]sum[i1][j1],i=0,j=0,i=0,j=0,other
矩阵和
s u m ( x 1 , y 1 , x 2 , y 2 ) = { s u m [ x 2 ] [ y 2 ] − s u m [ x 2 ] [ y 1 − 1 ] , x 1 = 0 s u m [ x 2 ] [ y 2 ] − s u m [ x 1 − 1 ] [ y 2 ] , y 1 = 0 s u m [ x 2 ] [ y 2 ] − s u m [ x 1 − 1 ] [ y 1 ] , y 1 = y 2 s u m [ x 2 ] [ y 2 ] − s u m [ x 1 − 1 ] [ y 2 ] − s u m [ x 2 ] [ y 1 − 1 ] + s u m [ x 1 − 1 ] [ y 1 − 1 ] , o t h e r sum(x1,y1,x2,y2) = \begin{cases} sum[x2][y2]-sum[x2][y1- 1]&,x1=0 \\ sum[x2][y2] - sum[x1-1][y2]&,y1=0\\ sum[x2][y2]-sum[x1-1][y1]&,y1=y2 \\ sum[x2][y2] - sum[x1-1][y2]-sum[x2][y1- 1]+sum[x1-1][y1-1]&,other \end{cases} sum(x1,y1,x2,y2)=sum[x2][y2]sum[x2][y11]sum[x2][y2]sum[x11][y2]sum[x2][y2]sum[x11][y1]sum[x2][y2]sum[x11][y2]sum[x2][y11]+sum[x11][y11],x1=0,y1=0,y1=y2,other

void pre_sum(int*){
    sum[0][0] = M[0][0]; // 第一个
    for(int i=1;i<n;i++) sum[i][0] = sum[i-1][0]+M[i][0]; // 第一列
    for(int j=1;j<m;j++) sum[0][j] = sum[0][j-1]+M[0][j]; // 第一行
    for(int i=1;i<n;i++)
        for(int j=1;j<m;j++)
            sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + M[i][j];
}
void get_sum(int x1,int y1, int x2, int y2){
    if(!x1 && !y1) return sum[x2][y2];
    if(!x1) return sum[x2][y2]-sum[x2][y1-1];
    if(!y1) return sum[x2][y2]-sum[x1-1][y2];
    return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}

二维差分

add(x1,y1,x2,y2,v)表示以(x1,y1)和(x2,y2)为对角顶点的部分同时加上v

以下基于全0数组记录操作差分:(原理和一维差分类似,就不列公式了)

int d[n+1][m+1] = {0}; // 边界扩大1,避免右/下边界判断
void add(x1,y1,x2,y2,v){
    d[x1][y1]+=v;
    d[x2+1][y1]-=v;
    d[x1][y2+1]-=v;
    d[x2+1][y2+1]+=v;
}
// 操作结束后,对d求pre_sum,得到delta_d,M+=delta_d即为操作结果
<think>嗯,用户想了解C++中的前缀和算法。首先,我需要回忆一下这两个概念。前缀和应该是一个数,其中每个元素是原数前i项的分的话,应该是原数相邻元素的,可能用于高效处理区间修改。 记得引用里提到前缀和可以快速计算区间,比如sum[r] - sum[l-1]。而分数则可以在O(1)时间内对区间进行加减操作,然后再通过前缀和恢复原数。这样比直接遍历修改数更高效,尤其是在多次操作的情况下。 用户提供的引用中有多个代码示例,一维二维的情况。我需要先解释一维前缀和的实现,比如如何构建前缀和,然后如何查询。然后是一维分,如何构建分数,进行区间修改,再通过前缀和得到结果。 可能用户需要知道这两个算法的作用优势。比如,前缀和适用于频繁查询区间,而分适用于频繁对区间进行增减操作。时间复杂度方面,构建前缀和是O(n),查询是O(1);分的构建也是O(n),修改区间是O(1),最后还原数是O(n)。 需要确保解释清晰,结构合理。先分别讲前缀和分,再给出代码示例,可能还要比较它们与传统方法的效率异。然后根据引用中的代码,说明具体的实现步骤,比如循环中的处理。 还要注意用户提到的系统级指令,比如数学公式用$...$,独立公式用$$...$$。例如,前缀和的公式可以写成$S[i] = S[i-1] + a[i]$,分的定义是$b[i] = a[i] - a[i-1]$。 可能用户还会想知道应用场景,比如前缀和在统计区间时的应用,分在批量更新区间时的优势。引用中的例子比如多次区间加减操作,用分可以大大减少时间。 需要检查引用的正确性,比如引用[1]提到分数的构建修改,引用[2]展示了一维前缀和的代码,引用[3]解释分是前缀和的逆运算,引用[4]给出了前缀和的构建代码,引用[5]对比了朴素方法分方法的效率。 然后生成相关问题,可能包括应用场景、优化方法、二维情况、时间复杂度比较等。比如如何用分处理二维情况,或者前缀和在动态规划中的应用。 在回答中要确保正确引用,比如在提到分优化时引用[3],在代码示例时引用对应的引用编号。同时,注意使用中文,避免使用Markdown格式,但代码块需要用```包裹。 最后,检查LaTeX语法是否正确,行内公式用$...$,独立公式用$$...$$并单独成段。例如,独立公式可以写成分数的定义: $$b[i] = a[i] - a[i-1]$$ 确保所有引用在回答末尾正确标注,如[^1]、[^2]等,并且与实际引用内容对应。</think>### C++中前缀和分的概念及实现 #### 一、前缀和 **概念** 前缀和是一种预处理技术,通过构建数$S$,其中$S[i]$表示原数$a$前$i$项的,即: $$S[i] = a[1] + a[2] + \dots + a[i]$$ 通过前缀和,可以在$O(1)$时间内计算任意区间$[l, r]$的:$S[r] - S[l-1]$[^4]。 **实现** ```cpp #include <iostream> using namespace std; int main() { int n, m; cin >> n >> m; int *a = new int[n + 1]; // 原数 int *S = new int[n + 1]; // 前缀和 S[0] = 0; // 初始化为0 for (int i = 1; i <= n; i++) { cin >> a[i]; S[i] = S[i - 1] + a[i]; // 计算前缀和 } while (m--) { int l, r; cin >> l >> r; cout << S[r] - S[l - 1] << endl; // 查询区间 } return 0; } ``` **应用场景** - 频繁查询数区间,如统计子数、滑动窗口问题[^2]。 --- #### 二、分 **概念** 分是前缀和的逆运算。若原数为$a$,其分数$b$满足: $$b[i] = a[i] - a[i - 1]$$ 通过分数,可以在$O(1)$时间内对区间$[l, r]$统一加减值$c$,最后通过前缀和还原$a$[^3]。 **实现** ```cpp #include <iostream> using namespace std; const int N = 1e5 + 10; int a[N], b[N]; int main() { int n, m; scanf("%d%d", &n, &m); // 构建分数 for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); b[i] = a[i] - a[i - 1]; } // 区间操作 while (m--) { int l, r, c; scanf("%d%d%d", &l, &r, &c); b[l] += c; // 左端点加c b[r + 1] -= c; // 右端点后减c } // 通过前缀和还原数 for (int i = 1; i <= n; i++) { a[i] = b[i] + a[i - 1]; printf("%d ", a[i]); } return 0; } ``` **应用场景** - 批量修改数区间值,如多次区间增减操作[^1]。 --- #### 三、性能对比 1. **前缀和** - 构建时间复杂度:$O(n)$ - 查询时间复杂度:$O(1)$ - 传统遍历求:$O(n)$[^5] 2. **分** - 构建时间复杂度:$O(n)$ - 区间修改时间复杂度:$O(1)$ - 传统遍历修改:$O(n)$[^5] --- #### 四、扩展:二维分与前缀和 对于二维数前缀和定义为: $$S[i][j] = S[i-1][j] + S[i][j-1] - S[i-1][j-1] + a[i][j]$$ 二维分则通过类似“积分”与“微分”的关系,支持子矩阵的快速修改[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值