前缀和 差分

本文详细介绍了如何在线获取一维和二维数组的前缀和与差分数组,以及它们在区间修改问题中的应用,包括修改和查询操作的复杂度分析。
摘要由CSDN通过智能技术生成

前缀和 差分

差分与前缀和为互逆运算,适用于区间修改+区间查询问题,可将区间修改转换为单点修改: d 差分 ⟶ ∑ a 原 ⟶ ∑ s 前缀和 \underset{差分}{d}\stackrel{\sum}{\longrightarrow}\underset{原}{a}\stackrel{\sum}{\longrightarrow}\underset{前缀和}{s} 差分da前缀和s

实现时通常数组下标从1开始,避免越界访问到负下标

一维前缀和 一维差分

几何意义:一维为线性的前缀运算,前缀和为长度

  • 一维前缀和: s i = s i − 1 + a i ⇒ a i = s i − s i − 1 s_i=s_{i-1}+a_i\Rightarrow a_i=s_i-s_{i-1} si=si1+aiai=sisi1
  • 第一类一维差分: d i = a i − a i − 1 ⇒ a i = a i − 1 + d i d_i=a_i-a_{i-1}\Rightarrow a_i=a_{i-1}+d_i di=aiai1ai=ai1+di
int n,a[n+1],s[n+1],d[n+2];
void init(){
    for(int i=1;i<=n;i++){
        s[i]=s[i-1]+a[i];
        d[i]=a[i]-a[i-1];
    }
}

预处理的复杂度为 O ( n ) O(n) O(n)

区间修改

设操作区间为 [ L , R ] [L,R] [L,R]

  • 差分数组中 d L + = Δ d_L+=\Delta dL+=Δ的单点修改,等价于原数组 a i + = Δ ( i ∈ [ L , + ∞ ] ) a_i+=\Delta(i\in[L,+\infty]) ai+=Δ(i[L,+])的区间修改
  • 若操作区间存在右端点 R R R,则需在 R + 1 R+1 R+1处撤销修改: d R + 1 − = Δ d_{R+1}-=\Delta dR+1=Δ,相当于原数组 a i − = Δ ( i ∈ [ R + 1 , + ∞ ] ) a_i-=\Delta(i\in[R+1,+\infty]) ai=Δ(i[R+1,+])
void modify(int l,int r,int d){
    d[l]+=d,d[r+1]-=d;
}

区间修改的复杂度为 O ( 1 ) O(1) O(1)

区间查询

  • 查询原数组:第二类一维差分 d d d初始化全部为0,用于存储原数组的每个元素的增量 Δ \Delta Δ。区间修改 [ L , R ] [L,R] [L,R]方法不变,之后求前缀和即为原数组每个元素的增量 Δ \Delta Δ

  • 查询前缀和: ∑ i = L R a i = ∑ i = 1 R a i − ∑ i = 1 L − 1 a i \sum\limits_{i=L}^{R}a_i=\sum\limits_{i=1}^{R}a_i-\sum\limits_{i=1}^{L-1}a_i i=LRai=i=1Raii=1L1ai

int query(int l,int r){
    //第一类一维差分
    for(int i=1;i<=n;i++){
        a[i]=d[i]+a[i-1];//更新原数组
        s[i]=a[i]+s[i-1];
	}
	return s[r]-s[l-1];
    //第二类一维差分
    for(int i=1;i<=n;i++){
        d[i]+=d[i-1];
    }
    for(int i=1;i<=n;i++){
        a[i]+=d[i];
        s[i]=s[i-1]+a[i];
    }
    return s[r]-s[l-1];
}

区间查询复杂度为 O ( n ) O(n) O(n)。由此可见差分数组擅长处理区间修改,但不擅长单点查询。

二维前缀和 二维差分

几何意义:二维为面的前缀运算,前缀和为面积

  • 二维前缀和 s [ i ] [ j ] = s [ i − 1 ] [ j ] 上侧所有部分 + s [ i ] [ j − 1 ] 左侧所有部分 − s [ i − 1 ] [ j − 1 ] 左上侧被加 2 次部分 + a [ i ] [ j ] 本身 s[i][j]=\underset{上侧所有部分}{s[i-1][j]}+\underset{左侧所有部分}{s[i][j-1]}-\underset{左上侧被加2次部分}{s[i-1][j-1]}+\underset{本身}{a[i][j]} s[i][j]=上侧所有部分s[i1][j]+左侧所有部分s[i][j1]左上侧被加2次部分s[i1][j1]+本身a[i][j]

  • 矩阵前缀和:设矩阵左上顶点为 ( x , y ) (x,y) (x,y),右下顶点为 ( i , j ) (i,j) (i,j) S = s [ i ] [ j ] − s [ x − 1 ] [ j ] 上侧非矩形部分 − s [ i ] [ y − 1 ] 左方非矩形部分 + s [ x − 1 ] [ y − 1 ] 左上方被减 2 次非矩形部分 S=s[i][j]-\underset{上侧非矩形部分}{s[x-1][j]}-\underset{左方非矩形部分}{s[i][y-1]}+\underset{左上方被减2次非矩形部分}{s[x-1][y-1]} S=s[i][j]上侧非矩形部分s[x1][j]左方非矩形部分s[i][y1]+左上方被减2次非矩形部分s[x1][y1]

  • 第一类二维差分 d [ i ] [ j ] = a [ i ] [ j ] − a [ i − 1 ] [ j ] 上侧所有部分 − a [ i ] [ j − 1 ] 左侧所有部分 + a [ i − 1 ] [ j − 1 ] 左上方被减 2 次部分 d[i][j]=a[i][j]-\underset{上侧所有部分}{a[i-1][j]}-\underset{左侧所有部分}{a[i][j-1]}+\underset{左上方被减2次部分}{a[i-1][j-1]} d[i][j]=a[i][j]上侧所有部分a[i1][j]左侧所有部分a[i][j1]+左上方被减2次部分a[i1][j1]

二维差分较为抽象,从前缀和与差分为互逆运算角度理解

int n,m,a[n+1][m+1],s[n+1][m+1],d[n+1][m+1];//n行m列
void init(){
    for(int i=1;j<=n;j++)
        for(int j=1;i<=m;i++){
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
            d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1];//第一类二维差分
        }
}

区间修改

设操作矩阵左上角坐标为 [ x 1 , y 1 ] [x_1,y_1] [x1,y1],右下角坐标为 [ x 2 , y 2 ] [x_2,y_2] [x2,y2]:

差分数组对 d [ i ] [ j ] + = Δ d[i][j]+=\Delta d[i][j]+=Δ的单点修改,等价于在原数组中的 [ i , j ] [i,j] [i,j] [ + ∞ , + ∞ ] [+\infty,+\infty] [+,+]范围的区间修改。因此, [ x 1 , y 1 ] [x_1,y_1] [x1,y1] [ x 2 , y 2 ] [x_2,y_2] [x2,y2]内中每个元素加 Δ \Delta Δ:

  1. d [ y 1 ] [ x 1 ] + = Δ d[y_1][x_1]+=\Delta d[y1][x1]+=Δ:原数组 [ y 1 , x 1 ] [y_1,x_1] [y1,x1] [ + ∞ , + ∞ ] + Δ [+\infty,+\infty]+\Delta [+,+]+Δ的二维区间修改
  2. d [ y 2 + 1 ] [ x 1 ] − = Δ d[y_2+1][x_1]-=\Delta d[y2+1][x1]=Δ:原数组 [ y 2 + 1 , x 1 ] [y_2+1,x_1] [y2+1,x1] [ + ∞ , + ∞ ] [+\infty,+\infty] [+,+]矩形中元素 − Δ -\Delta Δ(矩形右侧无需修改)
  3. d [ y 1 ] [ x 2 + 1 ] − = Δ d[y_1][x_2+1]-=\Delta d[y1][x2+1]=Δ:原数组 [ y 1 , x 2 + 1 ] [y_1,x_2+1] [y1,x2+1] [ + ∞ , + ∞ ] [+\infty,+\infty] [+,+]矩形中元素 − Δ -\Delta Δ(矩形下方无需修改)
  4. d [ y 2 + 1 ] [ x 2 + 1 ] + = Δ d[y_2+1][x_2+1]+=\Delta d[y2+1][x2+1]+=Δ:原数组 [ y 2 + 1 , x 2 + 1 ] [y_2+1,x_2+1] [y2+1,x2+1] [ + ∞ , + ∞ ] [+\infty,+\infty] [+,+]矩形中元素被减了2次 Δ \Delta Δ,撤销一次修改(矩形右下方)
void modify(int x1,int y1,int x2,int y2,int d){
    d[y1][x1]+=d;
    d[y2+1][x1]-=d;
    d[y1][x2+1]-=d;
    d[y2+1][x2+1]+=d;
}

区间查询

  • 查询原数组:第二类二维差分 d d d全部初始化为0,用于存储原数组内每个元素的增量 Δ \Delta Δ。区间修改方法不变,之后求前缀和即为原数组每个元素的增量 Δ \Delta Δ
  • 查询前缀和
int query(int x1,int y1,int x2,int y2){
    //第一类差分数组
    for(int j=1;j<=n;j++)
        for(int i=1;i<=m;i++){
            a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+d[i][j];//重新计算原数组
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
        }
    return s[y2][x2]-s[y1-1][x2]-s[y2][x1-1]+s[y1][x1];
    //第二类差分数组
    //第一类差分数组
    for(int j=1;j<=n;j++)
        for(int i=1;i<=m;i++)
            d[i][j]+=d[i-1][j]+d[i][j-1]-d[i-1][j-1];
    for(int j=1;j<=n;j++)
        for(int i=1;i<=m;i++)
            a[i][j]+=d[i][j];
}
  • 技巧:等价于将原数组所有值视为 d d d数组增量。元素初始全为0,进行 ∑ i = 1 n ∑ j = 1 m \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m} i=1nj=1m循环:

    • d [ i ] [ j ] + = a [ i ] [ j ] d[i][j]+=a[i][j] d[i][j]+=a[i][j]

    • d [ i + 1 ] [ j ] − = a [ i ] [ j ] d[i+1][j]-=a[i][j] d[i+1][j]=a[i][j]

    • d [ i ] [ j + 1 ] − = a [ i ] [ j ] d[i][j+1]-=a[i][j] d[i][j+1]=a[i][j]

    • d [ i + 1 ] [ j + 1 ] + = a [ i ] [ j ] d[i+1][j+1]+=a[i][j] d[i+1][j+1]+=a[i][j]

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

更高维的前缀和:三维前缀和,为对空间的前缀运算,几何意义为体积

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值