c++算法之基本算法篇 - 前缀和与差分

一、前缀和:快速计算区间和的神奇工具

1.1 什么是前缀和?生活中的比喻

想象你在超市购物,收银员需要快速计算你购买的商品总价:

  • 传统方法:每件商品价格相加(1+3+5+7+9=25)
  • 前缀和方法:提前计算"累计价格表"
    • 第1件:1
    • 第2件:1+3=4
    • 第3件:1+3+5=9
    • 第4件:1+3+5+7=16
    • 第5件:1+3+5+7+9=25

当需要计算第3到第5件商品价格时,只需用第5件的累计价格减去第2件的累计价格:25-4=21

前缀和就像提前准备好的"累计价格表",让我们能快速计算任意区间的和。

1.2 技术定义

前缀和(Prefix Sum)是一种预处理技术,通过预先计算数组的前n项和,使得任意区间和查询能在O(1)时间内完成。

对于数组a[1..n],前缀和数组S定义为:

S[0] = 0
S[1] = a[1]
S[2] = a[1] + a[2]
...
S[i] = a[1] + a[2] + ... + a[i]

1.3 为什么需要前缀和?

考虑这样一个问题:有一个包含100万个元素的数组,需要进行10万次区间和查询:

  • 暴力解法:每次查询需要遍历区间,时间复杂度O(n),总时间O(10万×100万)=O(100亿)
  • 前缀和解法:预处理O(n),每次查询O(1),总时间O(100万+10万)=O(110万)

前缀和将区间查询从线性时间优化到了常数时间!

1.4 一维前缀和的实现

#include <iostream>
#include <vector>
using namespace std;

// 计算前缀和数组
vector<int> computePrefixSum(const vector<int>& arr) {
    int n = arr.size();
    vector<int> prefix(n + 1, 0); // 多开一个空间,prefix[0]=0
    
    for (int i = 1; i <= n; i++) {
        prefix[i] = prefix[i - 1] + arr[i - 1];
    }
    
    return prefix;
}

// 查询区间和 [l, r] (0-based)
int rangeSum(const vector<int>& prefix, int l, int r) {
    return prefix[r + 1] - prefix[l];
}

int main() {
    vector<int> arr = {1, 3, 5, 7, 9};
    vector<int> prefix = computePrefixSum(arr);
    
    // 查询区间[1,3]的和(即3+5+7=15)
    cout << "区间[1,3]的和: " << rangeSum(prefix, 1, 3) << endl;
    
    return 0;
}

1.5 前缀和的典型应用场景

  1. 区间和查询:如统计某段时间内的销售额
  2. 平均值计算:快速计算任意区间的平均值
  3. 子数组问题:如寻找和为特定值的子数组
  4. 动态规划优化:某些DP问题可以用前缀和优化

二、一维差分:区间修改的高效工具

2.1 什么是差分?生活中的比喻

想象你是一个小区的水电工,需要记录每天的水表读数:

  • 周一:100升
  • 周二:120升(比周一多用20升)
  • 周三:150升(比周二多用30升)
  • 周四:140升(比周三少用10升)

这里的"每天用水量变化"就是差分。如果某天(周二到周四)需要临时增加用水量(每天多10升):

  • 传统方法:修改周二、周三、周四的读数(3次操作)
  • 差分方法:只在周二增加10升,在周五减少10升(2次操作)

差分就像记录"变化量"而不是"绝对值",使得区间修改更高效。

2.2 技术定义

差分(Difference)是前缀和的逆运算。对于数组a[1..n],差分数组d定义为:

d[1] = a[1]
d[2] = a[2] - a[1]
d[3] = a[3] - a[2]
...
d[i] = a[i] - a[i-1]

差分数组的一个重要性质:对差分数组求前缀和可以得到原数组。

2.3 为什么需要差分?

考虑这样一个问题:有一个包含100万个元素的数组,需要进行10万次区间修改(每次给区间[l,r]加上x):

  • 暴力解法:每次修改需要遍历区间,时间复杂度O(n),总时间O(10万×100万)=O(100亿)
  • 差分解法:每次修改O(1),最后求前缀和还原数组,总时间O(10万+100万)=O(110万)

差分将区间修改从线性时间优化到了常数时间!

2.4 一维差分的实现

#include <iostream>
#include <vector>
using namespace std;

// 初始化差分数组
vector<int> initDifference(const vector<int>& arr) {
    int n = arr.size();
    vector<int> diff(n);
    
    if (n > 0) {
        diff[0] = arr[0];
        for (int i = 1; i < n; i++) {
            diff[i] = arr[i] - arr[i - 1];
        }
    }
    
    return diff;
}

// 区间修改 [l, r] 加上 x (0-based)
void rangeAdd(vector<int>& diff, int l, int r, int x) {
    diff[l] += x;
    if (r + 1 < diff.size()) {
        diff[r + 1] -= x;
    }
}

// 从差分数组还原原数组
vector<int> restoreArray(const vector<int>& diff) {
    vector<int> arr(diff.size());
    if (diff.size() > 0) {
        arr[0] = diff[0];
        for (int i = 1; i < diff.size(); i++) {
            arr[i] = arr[i - 1] + diff[i];
        }
    }
    return arr;
}

int main() {
    vector<int> arr = {1, 3, 5, 7, 9};
    vector<int> diff = initDifference(arr);
    
    // 区间[1,3]加上2(即3+2, 5+2, 7+2)
    rangeAdd(diff, 1, 3, 2);
    
    vector<int> newArr = restoreArray(diff);
    cout << "修改后的数组: ";
    for (int num : newArr) {
        cout << num << " ";
    }
    cout << endl; // 输出: 1 5 7 9 9
    
    return 0;
}

2.5 差分的典型应用场景

  1. 区间修改:如给某段时间内的工资统一增加
  2. 多次更新:需要多次对数组进行区间修改
  3. 动态规划优化:某些DP问题可以用差分优化
  4. 图像处理:对图像的某个区域进行亮度调整

三、二维前缀和:平面区域的高效查询

3.1 什么是二维前缀和?生活中的比喻

想象你有一个城市的温度分布网格:

(1,1):20°C  (1,2):22°C  (1,3):21°C
(2,1):19°C  (2,2):23°C  (2,3):24°C
(3,1):18°C  (3,2):20°C  (3,3):19°C

现在需要快速计算某个区域(如从(1,1)到(2,2))的平均温度:

  • 传统方法:遍历该区域所有点求和(20+22+19+23=84)
  • 二维前缀和方法:提前计算"累计温度表"

二维前缀和就像为整个网格准备了一个"累计温度地图",让我们能快速计算任意矩形区域的和。

3.2 技术定义

二维前缀和(2D Prefix Sum)是二维数组的前n行m列的和。对于矩阵a[1..n][1..m],二维前缀和矩阵S定义为:

S[i][j] = a[1][1] + a[1][2] + ... + a[1][j]
        + a[2][1] + a[2][2] + ... + a[2][j]
        + ...
        + a[i][1] + a[i][2] + ... + a[i][j]

计算公式:

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

3.3 二维前缀和的查询公式

要查询子矩阵(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]

3.4 二维前缀和的实现

#include <iostream>
#include <vector>
using namespace std;

// 计算二维前缀和
vector<vector<int>> compute2DPrefixSum(const vector<vector<int>>& matrix) {
    int n = matrix.size();
    if (n == 0) return {};
    int m = matrix[0].size();
    
    vector<vector<int>> prefix(n + 1, vector<int>(m + 1, 0));
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            prefix[i][j] = prefix[i-1][j] + prefix[i][j-1] 
                         - prefix[i-1][j-1] + matrix[i-1][j-1];
        }
    }
    
    return prefix;
}

// 查询子矩阵和 [x1,y1] 到 [x2,y2] (0-based)
int rangeSum2D(const vector<vector<int>>& prefix, int x1, int y1, int x2, int y2) {
    return prefix[x2+1][y2+1] - prefix[x1][y2+1] 
         - prefix[x2+1][y1] + prefix[x1][y1];
}

int main() {
    vector<vector<int>> matrix = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    
    vector<vector<int>> prefix = compute2DPrefixSum(matrix);
    
    // 查询子矩阵[0,0]到[1,1]的和(即1+2+4+5=12)
    cout << "子矩阵[0,0]到[1,1]的和: " 
         << rangeSum2D(prefix, 0, 0, 1, 1) << endl;
    
    return 0;
}

3.5 二维前缀和的典型应用场景

  1. 图像处理:计算图像某个区域的像素和
  2. 游戏开发:计算游戏地图某个区域的资源总和
  3. 数据分析:统计二维数据表中某个子表的和
  4. 地理信息系统:计算地理区域的总降雨量

四、二维差分:平面区域的高效修改

4.1 什么是二维差分?生活中的比喻

想象你是一个农场主,管理一个田地的灌溉系统:

田地网格:
(1,1):正常  (1,2):正常  (1,3):正常
(2,1):正常  (2,2):正常  (2,3):正常
(3,1):正常  (3,2):正常  (3,3):正常

现在需要对某个矩形区域(如从(1,1)到(2,2))增加灌溉量:

  • 传统方法:修改该区域内每个点(4次操作)
  • 二维差分方法:在四个角点进行标记(4次操作,但更高效)

二维差分就像在田地的四个角设置"灌溉量变化标记",使得矩形区域修改更高效。

4.2 技术定义

二维差分(2D Difference)是二维前缀和的逆运算。对于矩阵a[1..n][1..m],二维差分矩阵d定义为:

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

二维差分的一个重要性质:对二维差分数组求二维前缀和可以得到原矩阵。

4.3 二维差分的修改操作

要对子矩阵(x1,y1)(x2,y2)加上值x

d[x1][y1] += x
d[x1][y2+1] -= x
d[x2+1][y1] -= x
d[x2+1][y2+1] += x

这个操作可以理解为:

  • 在左上角增加x:影响整个右下区域
  • 在右上角减少x:抵消对下方区域的影响
  • 在左下角减少x:抵消对右方区域的影响
  • 在右下角增加x:恢复被双重抵消的部分

4.4 二维差分的实现

#include <iostream>
#include <vector>
using namespace std;

// 初始化二维差分矩阵
vector<vector<int>> init2DDifference(const vector<vector<int>>& matrix) {
    int n = matrix.size();
    if (n == 0) return {};
    int m = matrix[0].size();
    
    vector<vector<int>> diff(n + 2, vector<int>(m + 2, 0));
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            diff[i+1][j+1] = matrix[i][j];
        }
    }
    
    // 计算差分矩阵
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            diff[i][j] -= diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1];
        }
    }
    
    return diff;
}

// 子矩阵修改 [x1,y1] 到 [x2,y2] 加上 x (0-based)
void rangeAdd2D(vector<vector<int>>& diff, int x1, int y1, int x2, int y2, int x) {
    diff[x1+1][y1+1] += x;
    diff[x1+1][y2+2] -= x;
    diff[x2+2][y1+1] -= x;
    diff[x2+2][y2+2] += x;
}

// 从二维差分矩阵还原原矩阵
vector<vector<int>> restore2DMatrix(const vector<vector<int>>& diff) {
    int n = diff.size() - 2;
    int m = diff[0].size() - 2;
    vector<vector<int>> matrix(n, vector<int>(m));
    
    // 计算前缀和还原原矩阵
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1];
            matrix[i-1][j-1] = diff[i][j];
        }
    }
    
    return matrix;
}

int main() {
    vector<vector<int>> matrix = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    
    vector<vector<int>> diff = init2DDifference(matrix);
    
    // 子矩阵[0,0]到[1,1]加上1
    rangeAdd2D(diff, 0, 0, 1, 1, 1);
    
    vector<vector<int>> newMatrix = restore2DMatrix(diff);
    cout << "修改后的矩阵:" << endl;
    for (const auto& row : newMatrix) {
        for (int num : row) {
            cout << num << " ";
        }
        cout << endl;
    }
    // 输出:
    // 2 3 3 
    // 5 6 6 
    // 7 8 9 
    
    return 0;
}

4.5 二维差分的典型应用场景

  1. 图像处理:对图像的某个矩形区域进行亮度调整
  2. 游戏开发:在游戏地图的某个区域添加特效
  3. 数据可视化:对热力图的某个区域进行高亮
  4. 模拟系统:模拟二维平面上的扩散过程

五、三维前缀和与差分:立体空间的高效操作

5.1 什么是三维前缀和?生活中的比喻

想象你有一个3D温度传感器网络,监测一个房间的温度分布:

层1:
(1,1,1):20°C  (1,1,2):22°C  (1,1,3):21°C
(1,2,1):19°C  (1,2,2):23°C  (1,2,3):24°C

层2:
(2,1,1):18°C  (2,1,2):20°C  (2,1,3):19°C
(2,2,1):17°C  (2,2,2):21°C  (2,2,3):22°C

现在需要快速计算某个立方体区域(如从(1,1,1)到(2,2,2))的平均温度:

  • 传统方法:遍历该立方体所有点求和(8次操作)
  • 三维前缀和方法:提前计算"累计温度立方体"

三维前缀和就像为整个3D空间准备了一个"累计温度立方体",让我们能快速计算任意长方体区域的和。

5.2 技术定义

三维前缀和(3D Prefix Sum)是三维数组的前i行j列k层的和。对于三维数组a[1..n][1..m][1..p],三维前缀和数组S定义为:

S[i][j][k] = a[1][1][1] + a[1][1][2] + ... + a[1][1][k]
           + a[1][2][1] + a[1][2][2] + ... + a[1][2][k]
           + ...
           + a[i][j][1] + a[i][j][2] + ... + a[i][j][k]

计算公式:

S[i][j][k] = S[i-1][j][k] + S[i][j-1][k] + S[i][j][k-1]
           - S[i-1][j-1][k] - S[i-1][j][k-1] - S[i][j-1][k-1]
           + S[i-1][j-1][k-1] + a[i][j][k]

5.3 三维前缀和的查询公式

要查询子立方体(x1,y1,z1)(x2,y2,z2)的和:

sum = S[x2][y2][z2] 
     - S[x1-1][y2][z2] - S[x2][y1-1][z2] - S[x2][y2][z1-1]
     + S[x1-1][y1-1][z2] + S[x1-1][y2][z1-1] + S[x2][y1-1][z1-1]
     - S[x1-1][y1-1][z1-1]

这个公式可以理解为容斥原理在三维空间的应用。

5.4 三维差分的修改操作

要对子立方体(x1,y1,z1)(x2,y2,z2)加上值x

d[x1][y1][z1] += x
d[x1][y1][z2+1] -= x
d[x1][y2+1][z1] -= x
d[x1][y2+1][z2+1] += x
d[x2+1][y1][z1] -= x
d[x2+1][y1][z2+1] += x
d[x2+1][y2+1][z1] += x
d[x2+1][y2+1][z2+1] -= x

这个操作在立方体的8个角点进行修改,利用容斥原理确保只有目标区域被修改。

5.5 三维前缀和与差分的实现

#include <iostream>
#include <vector>
using namespace std;

// 计算三维前缀和
vector<vector<vector<int>>> compute3DPrefixSum(const vector<vector<vector<int>>>& cube) {
    int n = cube.size();
    if (n == 0) return {};
    int m = cube[0].size();
    if (m == 0) return {};
    int p = cube[0][0].size();
    
    vector<vector<vector<int>>> prefix(n + 1, 
        vector<vector<int>>(m + 1, vector<int>(p + 1, 0)));
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 1; k <= p; k++) {
                prefix[i][j][k] = prefix[i-1][j][k] + prefix[i][j-1][k] + prefix[i][j][k-1]
                                 - prefix[i-1][j-1][k] - prefix[i-1][j][k-1] - prefix[i][j-1][k-1]
                                 + prefix[i-1][j-1][k-1] + cube[i-1][j-1][k-1];
            }
        }
    }
    
    return prefix;
}

// 查询子立方体和 [x1,y1,z1] 到 [x2,y2,z2] (0-based)
int rangeSum3D(const vector<vector<vector<int>>>& prefix, 
               int x1, int y1, int z1, int x2, int y2, int z2) {
    return prefix[x2+1][y2+1][z2+1] 
         - prefix[x1][y2+1][z2+1] - prefix[x2+1][y1][z2+1] - prefix[x2+1][y2+1][z1]
         + prefix[x1][y1][z2+1] + prefix[x1][y2+1][z1] + prefix[x2+1][y1][z1]
         - prefix[x1][y1][z1];
}

// 初始化三维差分数组
vector<vector<vector<int>>> init3DDifference(const vector<vector<vector<int>>>& cube) {
    int n = cube.size();
    if (n == 0) return {};
    int m = cube[0].size();
    if (m == 0) return {};
    int p = cube[0][0].size();
    
    vector<vector<vector<int>>> diff(n + 2, 
        vector<vector<int>>(m + 2, vector<int>(p + 2, 0)));
    
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            for (int k = 0; k < p; k++) {
                diff[i+1][j+1][k+1] = cube[i][j][k];
            }
        }
    }
    
    // 计算差分
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 1; k <= p; k++) {
                diff[i][j][k] -= diff[i-1][j][k] + diff[i][j-1][k] + diff[i][j][k-1]
                               - diff[i-1][j-1][k] - diff[i-1][j][k-1] - diff[i][j-1][k-1]
                               + diff[i-1][j-1][k-1];
            }
        }
    }
    
    return diff;
}

// 子立方体修改 [x1,y1,z1] 到 [x2,y2,z2] 加上 x (0-based)
void rangeAdd3D(vector<vector<vector<int>>>& diff, 
                int x1, int y1, int z1, int x2, int y2, int z2, int x) {
    diff[x1+1][y1+1][z1+1] += x;
    diff[x1+1][y1+1][z2+2] -= x;
    diff[x1+1][y2+2][z1+1] -= x;
    diff[x1+1][y2+2][z2+2] += x;
    diff[x2+2][y1+1][z1+1] -= x;
    diff[x2+2][y1+1][z2+2] += x;
    diff[x2+2][y2+2][z1+1] += x;
    diff[x2+2][y2+2][z2+2] -= x;
}

// 从三维差分数组还原原数组
vector<vector<vector<int>>> restore3DCube(const vector<vector<vector<int>>>& diff) {
    int n = diff.size() - 2;
    int m = diff[0].size() - 2;
    int p = diff[0][0].size() - 2;
    vector<vector<vector<int>>> cube(n, 
        vector<vector<int>>(m, vector<int>(p)));
    
    // 计算前缀和还原原数组
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 1; k <= p; k++) {
                diff[i][j][k] += diff[i-1][j][k] + diff[i][j-1][k] + diff[i][j][k-1]
                               - diff[i-1][j-1][k] - diff[i-1][j][k-1] - diff[i][j-1][k-1]
                               + diff[i-1][j-1][k-1];
                cube[i-1][j-1][k-1] = diff[i][j][k];
            }
        }
    }
    
    return cube;
}

int main() {
    vector<vector<vector<int>>> cube = {
        {{1, 2}, {3, 4}},
        {{5, 6}, {7, 8}}
    };
    
    // 三维前缀和示例
    vector<vector<vector<int>>> prefix = compute3DPrefixSum(cube);
    cout << "子立方体[0,0,0]到[1,1,1]的和: " 
         << rangeSum3D(prefix, 0, 0, 0, 1, 1, 1) << endl; // 1+2+3+4+5+6+7+8=36
    
    // 三维差分示例
    vector<vector<vector<int>>> diff = init3DDifference(cube);
    rangeAdd3D(diff, 0, 0, 0, 0, 0, 0, 10); // 给(0,0,0)位置加10
    
    vector<vector<vector<int>>> newCube = restore3DCube(diff);
    cout << "修改后的立方体:" << endl;
    for (int i = 0; i < newCube.size(); i++) {
        cout << "层" << i << ":" << endl;
        for (int j = 0; j < newCube[i].size(); j++) {
            for (int k = 0; k < newCube[i][j].size(); k++) {
                cout << newCube[i][j][k] << " ";
            }
            cout << endl;
        }
    }
    // 输出:
    // 层0:
    // 11 2 
    // 3 4 
    // 层1:
    // 5 6 
    // 7 8 
    
    return 0;
}

5.6 三维前缀和与差分的典型应用场景

  1. 科学计算:计算3D空间中某个区域的物理量总和
  2. 医学成像:分析CT或MRI扫描中某个体积的特征
  3. 游戏开发:管理3D游戏世界中的区域属性
  4. 气象学:分析大气中某个体积的气象数据

六、前缀和与差分的对比与选择

6.1 前缀和 vs 差分

特性前缀和差分
核心思想预处理区间和记录变化量
主要操作快速查询区间和快速修改区间
时间复杂度预处理O(n),查询O(1)修改O(1),还原O(n)
空间复杂度O(n)O(n)
适用场景多次查询,少量修改多次修改,少量查询
关系互为逆运算互为逆运算

6.2 如何选择?

  1. 查询密集型问题:选择前缀和

    • 例如:需要频繁查询不同区间的和
    • 典型场景:数据分析、统计计算
  2. 修改密集型问题:选择差分

    • 例如:需要频繁对数组进行区间修改
    • 典型场景:模拟系统、动态更新
  3. 混合操作问题

    • 如果查询和修改都很多,可能需要更高级的数据结构(如线段树、树状数组)
    • 如果可以分阶段处理(先修改后查询),可以先用差分修改,再用前缀和查询

6.3 维度选择

  1. 一维:线性数据,如时间序列、数组
  2. 二维:平面数据,如图像、矩阵
  3. 三维:立体数据,如3D模型、体积数据

选择维度取决于数据的实际结构,不要为了使用高维前缀和/差分而强行将低维数据转换为高维。

七、实战案例:前缀和与差分的综合应用

7.1 案例1:航班乘客统计(一维前缀和)

问题:某航空公司记录了每天航班的乘客数量,需要快速查询任意时间段内的总乘客数。

解决方案

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 每天乘客数量
    vector<int> passengers = {120, 150, 180, 200, 170, 190, 210};
    
    // 计算前缀和
    vector<int> prefix(passengers.size() + 1, 0);
    for (int i = 1; i <= passengers.size(); i++) {
        prefix[i] = prefix[i-1] + passengers[i-1];
    }
    
    // 查询第3天到第6天的总乘客数
    int day3 = 2, day6 = 5; // 0-based
    cout << "第3天到第6天的总乘客数: " 
         << prefix[day6+1] - prefix[day3] << endl; // 200+170+190=560
    
    return 0;
}

7.2 案例2:农田灌溉系统(二维差分)

问题:农场主有一个农田网格,需要多次对矩形区域进行灌溉量调整,最后输出每个地块的灌溉量。

解决方案

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n = 3, m = 3; // 3x3农田
    vector<vector<int>> farm(n, vector<int>(m, 0)); // 初始灌溉量
    
    // 初始化差分矩阵
    vector<vector<int>> diff(n + 2, vector<int>(m + 2, 0));
    
    // 进行多次灌溉调整
    // 操作1:区域(0,0)到(1,1)增加5单位
    diff[1][1] += 5;
    diff[1][3] -= 5;
    diff[3][1] -= 5;
    diff[3][3] += 5;
    
    // 操作2:区域(1,1)到(2,2)增加3单位
    diff[2][2] += 3;
    diff[2][4] -= 3;
    diff[4][2] -= 3;
    diff[4][4] += 3;
    
    // 还原农田灌溉量
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1];
            farm[i-1][j-1] = diff[i][j];
        }
    }
    
    // 输出最终灌溉量
    cout << "最终农田灌溉量:" << endl;
    for (const auto& row : farm) {
        for (int water : row) {
            cout << water << " ";
        }
        cout << endl;
    }
    // 输出:
    // 5 5 0 
    // 5 8 3 
    // 0 3 3 
    
    return 0;
}

7.3 案例3:3D温度分析(三维前缀和)

问题:科学家有一个3D温度传感器网络,需要分析某个立方体区域的平均温度。

解决方案

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 2x2x2的温度数据
    vector<vector<vector<int>>> temps = {
        {{20, 22}, {19, 23}},
        {{18, 20}, {17, 21}}
    };
    
    // 计算三维前缀和
    int n = temps.size(), m = temps[0].size(), p = temps[0][0].size();
    vector<vector<vector<int>>> prefix(n + 1, 
        vector<vector<int>>(m + 1, vector<int>(p + 1, 0)));
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 1; k <= p; k++) {
                prefix[i][j][k] = prefix[i-1][j][k] + prefix[i][j-1][k] + prefix[i][j][k-1]
                                 - prefix[i-1][j-1][k] - prefix[i-1][j][k-1] - prefix[i][j-1][k-1]
                                 + prefix[i-1][j-1][k-1] + temps[i-1][j-1][k-1];
            }
        }
    }
    
    // 查询整个立方体的平均温度
    int total = prefix[n][m][p];
    int count = n * m * p;
    cout << "整个立方体的平均温度: " << (double)total / count << "°C" << endl;
    // (20+22+19+23+18+20+17+21)/8 = 160/8 = 20°C
    
    return 0;
}

八、性能分析与优化技巧

8.1 时间复杂度分析

操作一维二维三维
前缀和预处理O(n)O(nm)O(nmp)
前缀和查询O(1)O(1)O(1)
差分修改O(1)O(1)O(1)
差分还原O(n)O(nm)O(nmp)

8.2 空间复杂度分析

数据结构一维二维三维
前缀和数组O(n)O(nm)O(nmp)
差分数组O(n)O(nm)O(nmp)

8.3 优化技巧

  1. 原地计算

    • 对于一维数组,可以直接在原数组上计算前缀和
    • 对于二维/三维数组,可以复用原数组空间(但会破坏原始数据)
  2. 滚动数组

    • 对于某些问题,可以只保留当前行/层的前缀和,节省空间
  3. 边界处理

    • 使用n+1大小的数组,避免边界条件判断
    • 在差分操作中,确保不越界(如if (r+1 < n)
  4. 数据类型选择

    • 根据数据范围选择合适的数据类型(intlong long等)
    • 防止整数溢出,特别是在计算前缀和时
  5. 缓存友好

    • 对于多维数组,按行优先顺序访问,提高缓存命中率
    • 在C++中使用vector<vector<vector<int>>>时,注意内存布局

九、常见问题与解决方案

9.1 整数溢出问题

问题:当数组元素很大或数组很长时,前缀和可能超出数据类型范围。

解决方案

// 使用更大的数据类型
vector<long long> prefix(n + 1, 0);
for (int i = 1; i <= n; i++) {
    prefix[i] = prefix[i-1] + (long long)arr[i-1];
}

9.2 多维数组索引混淆

问题:在二维/三维前缀和/差分中,容易混淆索引顺序。

解决方案

// 使用有意义的变量名
for (int layer = 0; layer < depth; layer++) {
    for (int row = 0; row < height; row++) {
        for (int col = 0; col < width; col++) {
            // 处理cube[layer][row][col]
        }
    }
}

9.3 差分还原顺序错误

问题:在还原差分数组时,顺序错误导致结果不正确。

解决方案

// 确保按正确顺序计算前缀和
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        // 先计算行方向
        diff[i][j] += diff[i][j-1];
    }
}
for (int j = 1; j <= m; j++) {
    for (int i = 1; i <= n; i++) {
        // 再计算列方向
        diff[i][j] += diff[i-1][j];
    }
}

9.4 多维前缀和公式记忆困难

问题:难以记住二维/三维前缀和的复杂公式。

解决方案

  • 理解容斥原理:加上所有低维前缀和,减去所有重复计算的部分
  • 使用递归思维:S[i][j][k] = S[i-1][j][k] + S[i][j-1][k] + S[i][j][k-1] - ...
  • 记忆模式:对于n维,需要考虑2^n-1种组合

十、总结与展望

10.1 核心要点回顾

  1. 前缀和

    • 核心思想:预处理区间和,实现O(1)查询
    • 适用场景:多次查询,少量修改
    • 扩展维度:一维→二维→三维
  2. 差分

    • 核心思想:记录变化量,实现O(1)区间修改
    • 适用场景:多次修改,少量查询
    • 与前缀和的关系:互为逆运算
  3. 多维扩展

    • 二维:平面区域操作
    • 三维:立体空间操作
    • 核心原理:容斥原理

10.2 学习建议

  1. 循序渐进

    • 先掌握一维前缀和和差分
    • 再扩展到二维
    • 最后挑战三维
  2. 实践应用

    • 在LeetCode上练习相关题目(如303, 304, 308等)
    • 尝试在实际项目中应用这些技术
    • 分析不同算法的性能差异
  3. 可视化理解

    • 画出前缀和和差分数组的结构
    • 手动模拟查询和修改过程
    • 理解容斥原理在多维空间的应用

10.3 进阶方向

  1. 算法扩展

    • 学习树状数组(Binary Indexed Tree)
    • 研究线段树(Segment Tree)
    • 探索更高级的区间操作数据结构
  2. 应用领域

    • 图像处理中的卷积运算
    • 数据库中的窗口函数
    • 机器学习中的特征工程
  3. 理论深化

    • 研究更高维的前缀和与差分
    • 探索动态版本的处理方法
    • 分析不同数据结构的时空权衡

前缀和与差分是算法设计中的基础而强大的工具,它们体现了"空间换时间"和"预处理"的重要思想。掌握这些技术不仅能解决特定问题,更能培养你的算法思维,为学习更高级的数据结构和算法打下坚实基础。

希望这篇详细的指南能帮助你彻底理解C++中的前缀和与差分技术,并在实际编程中灵活运用!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值