一、前缀和与差分基础概念

1. 前缀和的定义与特点

  • 前缀和思想:通过预处理构建一个前缀和数组/矩阵,用于快速计算区间和/子矩阵和

  • 适用场景:频繁查询数组或矩阵的区间和/子矩阵和

  • 核心特点:预处理时间复杂度 O(n)或 O(n2),查询时间复杂度 O(1)

2. 差分的定义与特点

  • 差分思想:通过构建差分数组/矩阵,实现对原数组的高效区间/子矩阵修改

  • 适用场景:需要对数组进行频繁的区间增减操作

  • 核心特点:区间更新时间复杂度 O(1),还原数组时间复杂度 O(n)或 O(n2)

3. 状态关系公式(通常:a为原数组,s前缀和数组,d差分数组)

一维前缀和:s[ ]

  • s[i]:表示原数组a中前i个元素的和(包含第i个元素)

    • 即将:s[i] = a[1] + a[2] + ... + a[i]

  • 构建前缀和数组:s[i]=s[i−1] + a[i]

  • 区间查询:sum[L,R] = s[R]−s[L−1]

一维差分: d[ ]

  • d[i]:表示原数组a中第i个元素与第i-1个元素的差值

  • 构建差分数组:d[i] = a[i] − a[i−1]

  • 区间更新([L,R]增加c):

    • d[L] += c

    • d[R+1] −= c

  • 还原数组:a[i] = a[i−1] + d[i]

二维前缀和::s[ ][ ]

  • s[i][j]:表示从原矩阵左上角(1,1)到右下角(i,j)形成的子矩阵中所有元素的和

    • 以(1,1)和(i,j)为对角线的矩形区域总和

  • 构建前缀和矩阵:s[i][j] = s[i−1][j]  +s[i][j−1] − s[i−1][j−1] + a[i][j]

  • 子矩阵查询:sum(x1,y1,x2,y2) = s[x2][y2] − s[x1−1][y2] − s[x2][y1−1] + s[x1−1][y1−1]

二维差分:d[ ][ ]

  • d[i][j]:表示原矩阵a中元素a[i][j]与其相邻元素的复合差值关系

  • 构建差分矩阵:d[i][j] = a[i][j] − a[i−1][j] − a[i][j−1] + a[i−1][j−1]

  • 子矩阵更新((x1,y1)到(x2,y2)增加c):

    d[x1][y1] += c
    d[x2+1][y1] -= c
    d[x1][y2+1] -= c
    d[x2+1][y2+1] += c
  • 还原数组:a[i][j] = a[i−1][j] + a[i][j−1] − a[i−1][j−1] + d[i][j]

状态关系总结表

状态类型

表示符号

状态定义

核心作用

一维前缀和

s[i]

前i项累加和

快速区间查询

一维差分

d[i]

相邻元素差值

高效区间修改

二维前缀和

s[i][j]

子矩阵累加和

快速子矩阵查询

二维差分

d[i][j]

复合差值关系

高效子矩阵修改

 

二、应用演示案例

演示1:一维前缀和(前n项和)

问题描述
小明每天记录自己的学习时长,现在他想知道前n天的总学习时长。给定一个包含每天学习时长的数组a,请帮他快速回答多次查询。

输入格式
第一行两个整数n和q,表示天数和查询次数
第二行n个整数,表示每天的学习时长
接下来q行,每行一个整数k,表示查询前k天的总时长

输出格式
对于每个查询,输出一行结果

输入样例

5 3
1 2 3 4 5
1
3
5

输出样例

1
6
15

解法代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N = 1e5+5;
 4 int s[N],a[N];
 5 
 6 int main() {
 7     int n, q;
 8     cin >> n >> q;
 9     for(int i=1; i<=n; i++) {
10         cin >> a[i];
11         s[i] = s[i-1] + a[i]; // 构建前缀和
12     }
13     while(q--) {
14         int k;
15         cin >> k;
16         cout << s[k] << endl;
17     }
18     return 0;
19 }

 

演示2:一维前缀和(区间和查询)

问题描述
某商店有n件商品,每件商品有一个价格。现在要进行促销活动,需要频繁计算某段区间内商品的总价格。请设计一个高效的系统来处理这些查询。

输入格式
第一行两个整数n和q,表示商品数量和查询次数
第二行n个整数,表示每件商品的价格
接下来q行,每行两个整数L和R,表示查询区间

输出格式
对于每个查询,输出一行结果

输入样例

5 3
10 20 30 40 50
1 3
2 5
3 3

输出样例

60
140
30

解法代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N = 1e5+5;
 4 int s[N],a[N];
 5 
 6 int main() {
 7     int n, q;
 8     cin >> n >> q;
 9     for(int i=1; i<=n; i++) {
10         cin >> a[i];
11         s[i] = s[i-1] + a[i]; // 构建前缀和
12     }
13     while(q--) {
14         int L, R;
15         cin >> L >> R;
16         cout << s[R] - s[L-1] << endl; // 区间和查询
17     }
18     return 0;
19 }

 

演示3:长度为k的区间和(滑动窗口前缀和)

问题描述
某气象站连续记录了n天的降水量数据。现在需要找出所有长度为k的连续天数区间中,降水量总和最大的那个区间。请设计一个高效算法解决这个问题。

输入格式
第一行两个整数n和k,表示天数和要求区间长度
第二行n个整数,表示每天的降水量

输出格式
一个整数,表示长度为k的区间中最大的降水量总和

输入样例1 

6 3
1 4 2 10 3 5

输出样例1

18

解释:区间[4,6]的和为10+3+5=18

输入样例2

5 2
5 2 1 8 3

输出样例2

11

解释:区间[4,5]的和8+3=11

解法分析

  1. 前缀和预处理:先计算前缀和数组s

  2. 滑动窗口计算:所有长度为k的区间和可以表示为s[i] - s[i-k](i从k到n)

  3. 维护最大值:遍历过程中维护最大的区间和

解法代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N = 1e5+5;
 4 int s[N],a[N];
 5 
 6 int main() {
 7     int n, k;
 8     cin >> n >> k;
 9     for(int i=1; i<=n; i++) {
10         cin >> a[i];
11         s[i] = s[i-1] + a[i]; // 构建前缀和
12     }
13     
14     int max_sum = INT_MIN;
15     for(int i=k; i<=n; i++) {
16         int current = s[i] - s[i-k]; // 计算长度为k的区间和
17         max_sum = max(max_sum, current);
18     }
19     
20     cout << max_sum << endl;
21     return 0;
22 }

 

演示4:一维差分(区间批量更新)

问题描述
某游戏有n个关卡,每个关卡有一个初始难度。现在要进行m次调整,每次将第L到第R关的难度增加c。请输出最终每个关卡的难度。

输入格式
第一行两个整数n和m
第二行n个整数,表示各关卡的初始难度
接下来m行,每行三个整数L, R, c

输出格式
一行n个整数,表示最终每个关卡的难度

输入样例

5 3
3 1 4 2 5
1 3 2
2 5 1
3 4 -1

输出样例

5 4 6 2 6

解法分析

  1. 差分数组构建:根据初始数组构建差分数组d,其中d[i] = a[i] - a[i-1](a[0]=0)

  2. 区间更新:对差分数组进行区间更新操作

  3. 还原数组:通过差分数组还原最终结果数组

解法代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N = 1e5+5;
 4 int a[N], d[N];
 5 
 6 int main() {
 7     int n, m;
 8     cin >> n >> m;
 9     
10     // 输入初始难度并构建差分数组
11     for(int i=1; i<=n; i++) {
12         cin >> a[i];
13         d[i] = a[i] - a[i-1];
14     }
15     
16     // 执行m次区间更新
17     while(m--) {
18         int L, R, c;
19         cin >> L >> R >> c;
20         d[L] += c;
21         d[R+1] -= c;
22     }
23     
24     // 还原最终难度数组
25     for(int i=1; i<=n; i++) {
26         a[i] = a[i-1] + d[i];
27         cout << a[i] << " ";
28     }
29     
30     return 0;
31 }

演示5:二维前缀和(子矩阵和查询)

问题描述
某农场被划分成n×n的网格,每个网格有一定数量的苹果。现在要统计多个矩形区域内的苹果总数。

输入格式
第一行一个整数n
接下来n行,每行n个整数表示每个网格的苹果数
接下来一行一个整数q
接下来q行,每行四个整数x1,y1,x2,y2表示查询的矩形区域

输出格式
对于每个查询,输出一行结果

输入样例

3
1 2 3
4 5 6
7 8 9
2
1 1 2 2
2 2 3 3

输出样例

12
28

解法代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int s[N][N],a[N][N];

int main() {
    int n;
    cin >> n;
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=n; j++) {
            cin >> a[i][j];
            s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]; // 构建二维前缀和
        }
    }
    int q;
    cin >> q;
    while(q--) {
        int x1,y1,x2,y2;
        cin >> x1 >> y1 >> x2 >> y2;
        int res = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]; // 子矩阵查询
        cout << res << endl;
    }
    return 0;
}

 

三、如何识别前缀和/差分问题

1. 前缀和问题的识别特征

  • 关键词:频繁查询区间和、子矩阵和、静态数据(数据不修改)

  • 场景特征

    • 需要多次计算数组某段区间的累加和

    • 需要计算矩阵中某个子矩阵的元素和

    • 题目强调查询次数多,要求查询时间尽可能快

2. 差分问题的识别特征

  • 关键词:批量区间修改、多次更新后查询、离线处理

  • 场景特征

    • 需要对数组的某个区间进行统一增减操作

    • 需要先进行多次修改,最后才查询结果

    • 题目强调修改次数多,要求修改时间尽可能快

3. 综合判断技巧

  1. 如果题目既有区间修改又有区间查询,可能需要结合线段树或树状数组

  2. 前缀和通常用于"静态数据+频繁查询"场景

  3. 差分通常用于"频繁修改+最后查询"场景

  4. 当数据维度从一维变为二维时,考虑二维前缀和/差分

典型例题模式

  • "给定一个数组,进行q次查询,每次查询区间[L,R]的和" → 前缀和

  • "给定一个数组,进行q次操作,每次将[L,R]区间内的数加c,最后输出数组" → 差分

  • "给定一个矩阵,查询多个子矩阵的和" → 二维前缀和

  • "给定一个矩阵,对多个子矩阵进行增减操作,最后输出矩阵" → 二维差分