前缀和与差分

前缀和与差分算法详解

前缀和

前缀和(Prefix Sum)是一种高效的数组预处理技巧,核心是通过构建“前缀和数组”,将区间和计算从传统的线性时间(O(n))优化为常数时间(O(1))。其本质是“预存储前i个元素的累加和”,后续查询区间和时直接通过查表推导,避免重复计算。

适用场景

  • 频繁查询数组中任意区间的元素和(如子数组和、子矩阵和);
  • 解决与区间和相关的衍生问题(如子数组和的最大值、平均值、是否存在目标和等)。

差分

差分(Difference)是与前缀和互补的数组预处理技巧,核心是通过构建“差分数组”,将区间更新操作(如给某区间所有元素加/减一个值)从线性时间(O(n))优化为常数时间(O(1))。其本质是“记录相邻元素的差值”,区间更新时仅需修改差分数组的两个端点,最后通过前缀和还原出原数组。

关键性质

  • 差分数组的前缀和等于原数组;
  • 原数组的相邻元素差值等于差分数组(即差分是前缀和的逆操作)。

适用场景

  • 频繁对数组的任意区间进行统一更新(如给 [x,y] 区间所有元素加 z);
  • 结合前缀和解决“区间更新+区间查询”的复合问题。

一维数组的前缀和与差分

一维数组是前缀和与差分的基础场景,所有操作围绕“线性结构”展开,逻辑清晰且易于理解。

一维数组前缀和

核心定义

设原始一维数组为 a[n](下标从 1 开始,a[0] = 0,避免边界判断),其前缀和数组 sum[n] 定义为:
sum[i] = a[1] + a[2] + ... + a[i]
递推公式:sum[i] = sum[i-1] + a[i](前i个元素的和 = 前i-1个元素的和 + 第i个元素)。

示例与代码实现

以数组 a = [0, 1, 2, 3, 4, 5]a[0]=0,有效元素为 a[1]~a[5])为例,前缀和数组 sum 的计算过程如下:

下标 i 0 1 2 3 4 5
a[i] 0 1 2 3 4 5
sum[i] 0 1(sum[0]+a[1]) 3(sum[1]+a[2]) 6(sum[2]+a[3]) 10(sum[3]+a[4]) 15(sum[4]+a[5])

代码实现

#include<bits/stdc++.h>
using namespace std;

int main() {
    // 原始数组:a[0]=0(占位),a[1]~a[5]为有效元素
    int a[6] = { 0, 1, 2, 3, 4, 5 };
    // 前缀和数组:sum[0]=0,sum[i]存储a[1]~a[i]的和
    int sum[6];
    sum[0] = a[0]; // sum[0] = 0

    // 计算前缀和(递推公式)
    for (int i = 1; i <= 5; i++) {
        sum[i] = sum[i - 1] + a[i];
    }

    // 输出结果
    cout << "一维数组a(有效元素):";
    for (int i = 1; i <= 5; i++) {
        cout << a[i] << ' ';
    }
    cout << "\n一维前缀和sum(有效元素):";
    for (int i = 1; i <= 5; i++) {
        cout << sum[i] << ' ';
    }

    return 0;
}

运行结果

一维数组a(有效元素):1 2 3 4 5 
一维前缀和sum(有效元素):1 3 6 10 15 

简单应用:查询区间和

需求:给定数组 a,快速计算区间 [r, l](从第r个元素到第l个元素)的和。
核心公式区间和 = sum[l] - sum[r-1]
(解释:sum[l]a[1]~a[l] 的和,sum[r-1]a[1]~a[r-1] 的和,两者相减即得到 a[r]~a[l] 的和)。

代码实现

#include<bits/stdc++.h>
using namespace std;

int main() {
    int a[6] = { 0, 1, 2, 3, 4, 5 };
    int sum[6];
    sum[0] = a[0];

    // 1. 预处理前缀和数组
    for (int i = 1; i <= 5; i++) {
        sum[i] = sum[i - 1] + a[i];
    }

    // 2. 输入查询区间 [r, l]
    int r, l;
    cout << "输入数组区间[r, l](r ≤ l):";
    cin >> r >> l;

    // 3. 计算并输出区间和
    int intervalSum = sum[l] - sum[r - 1];
    cout << "数组在区间[" << r << "," << l << "]的和:" << intervalSum;

    return 0;
}

测试案例

  • 输入:2 4(查询 a[2]~a[4] 的和:2+3+4=9);
  • 运行结果:数组在区间[2,4]的和:9

一维数组差分

核心定义

设原始一维数组为 a[n](下标从 1 开始,a[0] = 0),其差分数组 diff[n] 定义为:
diff[i] = a[i] - a[i-1](第i个元素与第i-1个元素的差值)。

示例与代码实现

以数组 a = [0, 1, 2, 3, 4, 5] 为例,差分数组 diff 的计算过程如下:

下标 i 0 1 2 3 4 5
a[i] 0 1 2 3 4 5
diff[i] 0 1-0=1 2-1=1 3-2=1 4-3=1 5-4=1

代码实现

#include<bits/stdc++.h>
using namespace std;

int main() {
    // 原始数组:a[0]=0,a[1]~a[5]为有效元素
    int a[6] = { 0, 1, 2, 3, 4, 5 };
    // 差分数组:diff[0]=0,diff[i] = a[i] - a[i-1]
    int diff[6];
    diff[0] = a[0]; // diff[0] = 0

    // 计算差分数组
    for (int i = 1; i <= 5; i++) {
        diff[i] = a[i] - a[i - 1];
    }

    // 输出结果
    cout << "一维数组a(有效元素):";
    for (int i = 1; i <= 5; i++) {
        cout << a[i] << ' ';
    }
    cout << "\n一维差分数组diff(有效元素):";
    for (int i = 1; i <= 5; i++) {
        cout << diff[i] << ' ';
    }

    return 0;
}

运行结果

一维数组a(有效元素):1 2 3 4 5 
一维差分数组diff(有效元素):1 1 1 1 1 

简单应用:区间更新与原数组还原

需求:给数组 a 的区间 [x, y] 内所有元素增加一个值 z,然后还原出更新后的数组 a
传统方法:遍历 [x, y] 区间,逐个给元素加 z,时间复杂度 O(y-x+1);
差分方法:仅需修改差分数组的两个端点,时间复杂度 O(1),再通过差分数组的前缀和还原原数组。

核心操作(区间更新)
  1. diff[x] += z:表示从第x个元素开始,所有后续元素都增加z;
  2. diff[y+1] -= z:表示从第y+1个元素开始,抵消前面增加的z(避免影响区间外的元素)。
原数组还原

更新差分数组后,对 diff 求前缀和,即可得到更新后的原数组 a,公式为:a[i] = a[i-1] + diff[i]

代码实现

#include<bits/stdc++.h>
using namespace std;

int main() {
    // 原始数组:a[0]=0,a[1]~a[5]为有效元素
    int a[6] = { 0, 1, 2, 3, 4, 5 };
    // 差分数组
    int diff[6];
    diff[0] = a[0];

    // 1. 预处理差分数组
    for (int i = 1; i <= 5; i++) {
        diff[i] = a[i] - a[i - 1];
    }

    // 2. 输入区间更新参数:[x,y] 区间加 z
    int x, y, z;
    cout << "输入数组区间[x,y](x ≤ y)及需要加的z值:";
    cin >> x >> y >> z;

    // 3. 差分数组更新(仅修改两个端点)
    diff[x] += z;
    if (y + 1 <= 5) { // 避免越界(若y是最后一个元素,y+1超出数组范围,无需操作)
        diff[y + 1] -= z;
    }

    // 4. 从差分数组还原更新后的原数组
    for (int i = 1; i <= 5; i++) {
        a[i] = a[i - 1] + diff[i];
    }

    // 5. 输出结果
    cout << "更新后的一维数组a(有效元素):";
    for (int i = 1; i <= 5; i++) {
        cout << a[i] << ' ';
    }

    return 0;
}

测试案例

  • 输入:2 4 1(给 a[2]~a[4] 加 1,即 2→3、3→4、4→5);
  • 运行结果:更新后的一维数组a(有效元素):1 3 4 5 5

二维数组的前缀和与差分

二维数组(矩阵)的前缀和与差分是一维场景的扩展,核心是处理“子矩阵和”与“子矩阵更新”,逻辑上需要考虑行和列两个维度的叠加。

二维数组前缀和

核心定义

设原始二维数组为 a[n][m](下标从 1 开始,a[0][j] = a[i][0] = 0,避免边界判断),其二维前缀和数组 sum[n][m] 定义为:
sum[i][j] = 以 (1,1) 为左上角、(i,j) 为右下角的子矩阵的所有元素和

递推公式与原理

二维前缀和的计算需要考虑“行前缀和”与“列前缀和”的重叠部分,递推公式为:
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j]

原理图解

  • sum[i-1][j]:上方子矩阵的和(行 1~i-1,列 1~j);
  • sum[i][j-1]:左侧子矩阵的和(行 1~i,列 1~j-1);
  • sum[i-1][j-1]:上方与左侧重叠的子矩阵的和(被重复加了两次,需减去一次);
  • a[i][j]:当前单元格的值(最后加上,构成完整的 (1,1)~(i,j) 子矩阵)。

示例与代码实现

以 3×3 矩阵 aa[0][*] = a[*][0] = 0)为例,前缀和数组 sum 的计算过程如下:

原始数组 a(有效区域:a[1][1]~a[3][3]):

a[i][j] 0 1 2 3
0 0 0 0 0
1 0 1 2 3
2 0 2 1 3
3 0 3 2 1

前缀和数组 sum

sum[i][j] 0 1 2 3
0 0 0 0 0
1 0 1(sum[0][1]+sum[1][0]-sum[0][0]+a[1][1]) 3(sum[0][2]+sum[1][1]-sum[0][1]+a[1][2]) 6(sum[0][3]+sum[1][2]-sum[0][2]+a[1][3])
2 0 3(sum[1][1]+sum[2][0]-sum[1][0]+a[2][1]) 6(sum[1][2]+sum[2][1]-sum[1][1]+a[2][2]) 12(sum[1][3]+sum[2][2]-sum[1][2]+a[2][3])
3 0 6(sum[2][1]+sum[3][0]-sum[2][0]+a[3][1]) 11(sum[2][2]+sum[3][1]-sum[2][1]+a[3][2]) 18(sum[2][3]+sum[3][2]-sum[2][2]+a[3][3])

代码实现

#include<bits/stdc++.h>
using namespace std;

int main() {
    // 关闭同步流,加速输入输出(竞赛常用)
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    // 原始二维数组:a[0][*] = a[*][0] = 0(占位)
    int a[4][4] = { 
        {0, 0, 0, 0},
        {0, 1, 2, 3},
        {0, 2, 1, 3},
        {0, 3, 2, 1}
    };
    // 二维前缀和数组:sum[0][*] = sum[*][0] = 0
    int sum[4][4] = { 0 };

    // 计算二维前缀和(递推公式)
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
        }
    }

    // 输出原始数组和前缀和数组(验证)
    cout << "原始数组a(有效区域):\n";
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            cout << a[i][j] << "  ";
        }
        cout << '\n';
    }

    cout << "二维前缀和数组sum(有效区域):\n";
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            cout << sum[i][j] << "  ";
        }
        cout << '\n';
    }

    return 0;
}

运行结果

原始数组a(有效区域):
1  2  3  
2  1  3  
3  2  1  

二维前缀和数组sum(有效区域):
1  3  6  
3  6  12  
6  11  18  

简单应用:查询子矩阵和

需求:给定二维数组 a,快速计算以 (x1,y1) 为左上角、(x2,y2) 为右下角的子矩阵的和。
核心公式
子矩阵和 = sum[x2][y2] - sum[x2][y1-1] - sum[x1-1][y2] + sum[x1-1][y1-1]

原理图解

  • sum[x2][y2](1,1)~(x2,y2) 的完整子矩阵和;
  • sum[x2][y1-1](1,1)~(x2,y1-1) 的左侧子矩阵和(需减去);
  • sum[x1-1][y2](1,1)~(x1-1,y2) 的上方子矩阵和(需减去);
  • sum[x1-1][y1-1](1,1)~(x1-1,y1-1) 的重叠子矩阵和(被重复减了两次,需加回一次)。

代码实现

#include<bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    // 1. 初始化原始数组和前缀和数组
    int a[4][4] = { 
        {0, 0, 0, 0},
        {0, 1, 2, 3},
        {0, 2, 1, 3},
        {0, 3, 2, 1}
    };
    int sum[4][4] = { 0 };

    // 2. 计算二维前缀和
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
        }
    }

    // 3. 输入子矩阵的对角坐标 (x1,y1)(左上)和 (x2,y2)(右下)
    int x1, y1, x2, y2;
    cout << "输入子矩阵坐标 (x1,y1) 和 (x2,y2)(x1 ≤ x2,y1 ≤ y2):";
    cin >> x1 >> y1 >> x2 >> y2;

    // 4. 计算并输出子矩阵和
    int matrixSum = sum[x2][y2] - sum[x2][y1 - 1] - sum[x1 - 1][y2] + sum[x1 - 1][y1 - 1];
    cout << "子矩阵 (" << x1 << "," << y1 << ")~(" << x2 << "," << y2 << ") 的和:" << matrixSum;

    return 0;
}

测试案例

  • 输入:1 1 3 2(子矩阵为 a[1][1]~a[3][2],元素为 1、2、2、1、3、2,和为 11);
  • 运行结果:子矩阵 (1,1)~(3,2) 的和:11

二维数组差分

核心定义

设原始二维数组为 a[n][m](下标从 1 开始,a[0][j] = a[i][0] = 0),其二维差分数组 diff[n][m] 定义为:
diff[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]
(本质是二维前缀和的逆操作:差分数组的二维前缀和等于原数组)。

示例与代码实现

以 3×3 矩阵 a 为例,差分数组 diff 的计算过程如下:

原始数组 a(有效区域:a[1][1]~a[3][3]):

a[i][j] 0 1 2 3
0 0 0 0 0
1 0 1 2 3
2 0 2 1 3
3 0 3 2 1

差分数组 diff(按公式计算):

diff[i][j] 0 1 2 3
0 0 0 0 0
1 0 1-0-0+0=1 2-0-1+0=1 3-0-2+0=1
2 0 2-1-0+0=1 1-2-2+1=-2 3-3-1+2=1
3 0 3-2-0+0=1 2-3-1+2=0 1-3-2+1=-3

代码实现

#include<bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    // 原始二维数组:a[0][*] = a[*][0] = 0
    int a[4][4] = { 
        {0, 0, 0, 0},
        {0, 1, 2, 3},
        {0, 2, 1, 3},
        {0, 3, 2, 1}
    };
    // 二维差分数组:diff[0][*] = diff[*][0] = 0
    int diff[4][4] = { 0 };

    // 计算二维差分数组(公式:diff[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1])
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            diff[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1];
        }
    }

    // 输出结果
    cout << "原始数组a(有效区域):\n";
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            cout << a[i][j] << "  ";
        }
        cout << '\n';
    }

    cout << "二维差分数组diff(有效区域):\n";
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            cout << diff[i][j] << "  ";
        }
        cout << '\n';
    }

    return 0;
}

运行结果

原始数组a(有效区域):
1  2  3  
2  1  3  
3  2  1  

二维差分数组diff(有效区域):
1  1  1  
1  -2  1  
1  0  -3  

简单应用:子矩阵更新与原数组还原

需求:给二维数组 a 中以 (x1,y1) 为左上、(x2,y2) 为右下的子矩阵所有元素增加 z,然后还原出更新后的数组 a

核心操作(子矩阵更新)
仅需修改差分数组的 4 个端点,时间复杂度 O(1),操作如下:

  1. diff[x1][y1] += z:子矩阵左上角开始,所有元素增加 z;
  2. diff[x1][y2+1] -= z:子矩阵右上角右侧,抵消 z(避免影响右侧区域);
  3. diff[x2+1][y1] -= z:子矩阵左下角下方,抵消 z(避免影响下方区域);
  4. diff[x2+1][y2+1] += z:子矩阵右下角的右下角,恢复 z(抵消前两步的重叠抵消,避免影响重叠区域)。

原数组还原
更新差分数组后,对 diff 求二维前缀和,得到“更新量矩阵”,再将其与原始数组 a 相加,即可得到更新后的 a

代码实现

#include<bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    // 1. 初始化原始数组
    int a[4][4] = { 
        {0, 0, 0, 0},
        {0, 1, 2, 3},
        {0, 2, 1, 3},
        {0, 3, 2, 1}
    };
    // 二维差分数组(初始为0,用于记录更新操作)
    int diff[4][4] = { 0 };

    // 2. 输出更新前的原始数组
    cout << "更新前的数组a:\n";
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            cout << a[i][j] << "  ";
        }
        cout << '\n';
    }

    // 3. 输入子矩阵更新参数:(x1,y1)~(x2,y2) 加 z
    int x1, y1, x2, y2, z;
    cout << "输入子矩阵坐标 (x1,y1)、(x2,y2) 及增加的z值:";
    cin >> x1 >> y1 >> x2 >> y2 >> z;

    // 4. 差分数组更新(修改4个端点)
    diff[x1][y1] += z;
    if (y2 + 1 <= 3) diff[x1][y2 + 1] -= z;
    if (x2 + 1 <= 3) diff[x2 + 1][y1] -= z;
    if (x2 + 1 <= 3 && y2 + 1 <= 3) diff[x2 + 1][y2 + 1] += z;

    // 5. 计算差分数组的二维前缀和(得到更新量矩阵 sumdiff)
    int sumdiff[4][4] = { 0 };
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            sumdiff[i][j] = sumdiff[i - 1][j] + sumdiff[i][j - 1] + diff[i][j] - sumdiff[i - 1][j - 1];
        }
    }

    // 6. 原始数组加上更新量,得到最终数组
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            a[i][j] += sumdiff[i][j];
        }
    }

    // 7. 输出更新后的数组
    cout << "更新后的数组a:\n";
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            cout << a[i][j] << "  ";
        }
        cout << '\n';
    }

    return 0;
}

测试案例

  • 输入:1 2 3 3 2(给子矩阵 (1,2)~(3,3) 加 2);
  • 原始子矩阵元素:2→4、3→5、1→3、3→5、2→4、1→3;
  • 运行结果:
更新前的数组a:
1  2  3  
2  1  3  
3  2  1  

更新后的数组a:
1  4  5  
2  3  5  
3  4  3  
posted @ 2025-10-29 13:43  Jing61  阅读(45)  评论(0)    收藏  举报