前缀和&差分

一、前缀和(Prefix Sum)

前缀和的核心思想是预先计算并存储累积信息,将区间求和操作从 O(n) 优化到 O(1),特别适合需要频繁查询子数组或子矩阵和的场景。适用于需要频繁计算 “子数组 / 子矩阵和” 的场景。

1. 一维前缀和

定义:对于数组a[1..n](建议从 1 开始索引,避免边界判断),其前缀和数组s[1..n]满足:
s[i] = a[1] + a[2] + ... + a[i](即前 i 个元素的累加和)。

计算公式
通过 “递推” 快速构造前缀和数组:
s[0] = 0
s[i] = s[i-1] + a[i](i≥1)

核心应用:快速求子数组和
若需计算a[l..r](从第 l 个到第 r 个元素)的和,直接用前缀和相减:
sum(l, r) = s[r] - s[l-1]

举例
a = [1, 2, 3, 4, 5](索引 1-5),则前缀和数组s为:
s[0]=0,s[1]=1,s[2]=3(1+2),s[3]=6(1+2+3),s[4]=10,s[5]=15
a[2..4]的和:s[4] - s[1] = 10 - 1 = 9

2. 二维前缀和(矩阵前缀和)

定义:对于二维数组(矩阵)a[1..n][1..m],其前缀和数组s[1..n][1..m]满足:
s[i][j] = 所有a[x][y]的和(其中x≤i,y≤j)(即左上角 (1,1) 到右下角 (i,j) 的矩形内所有元素和)。

计算公式(建议不要死记,画图+容斥原理推导)
需避免重复累加(s[i-1][j]s[i][j-1]均包含s[i-1][j-1]),公式为:
s[i][j] = a[i][j] + s[i-1][j] + s[i][j-1] - s[i-1][j-1]
s[0][*] = 0s[*][0] = 0,辅助边界)

核心应用:快速求子矩阵和
若需计算 “左上角 (x1,y1) 到右下角 (x2,y2)” 的子矩阵和,公式为:
sum(x1,y1,x2,y2) = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]

举例
设 3x3 矩阵a如下(索引 1-3):

1 2 3  
4 5 6  
7 8 9  

其前缀和s[2][2](对应 (1,1)-(2,2) 的矩形)计算:
s[2][2] = 5 + s[1][2](1+2=3) + s[2][1](1+4=5) - s[1][1](1) = 5+3+5-1=12
求 (2,2)-(3,3) 的子矩阵和(5+6+8+9=28):
s[3][3](45) - s[1][3](6) - s[3][1](12) + s[1][1](1) = 45-6-12+1=28

二、差分(Difference)

差分的核心是通过 “差分数组” 记录 “原数组的变化量”,将 “区间更新” 从 O (n) 优化到 O (1)。适用于需要频繁对 “区间元素做统一加减” 的场景。

1. 一维差分

定义:对于数组a[1..n],其差分数组d[1..n]满足:a 是 d 的前缀和。即:
d[1] = a[1]
d[i] = a[i] - a[i-1](i≥2)

反过来,若已知d,通过 “前缀和” 可还原a
a[i] = d[1] + d[2] + ... + d[i](即 d 的前缀和为 a)。

核心应用:快速区间更新
若需对a[l..r]的所有元素 “加 k”(或减 k),无需逐个修改 a,只需操作差分数组 d:

  • d[l] += k(从 l 开始,所有 a [i](i≥l)都会因 d 的前缀和增加 k)
  • d[r+1] -= k(从 r+1 开始,抵消 k 的影响,避免 a [i](i>r)被误改)

最后通过 “d 的前缀和” 即可得到更新后的 a 数组。

举例
原数组a = [1, 2, 3, 4, 5],差分数组d为:
d[1]=1,d[2]=2-1=1,d[3]=3-2=1,d[4]=4-3=1,d[5]=5-4=1(d 全为 1,符合 a 是 d 的前缀和:1,1+1=2,1+1+1=3...)。

需求:给a[2..4]的元素各加 2(目标 a 变为 [1,4,5,6,5])。
操作 d:

  • d[2] += 2 → d [2] 变为 3
  • d[5] -= 2(因 r=4,r+1=5)→ d [5] 变为 - 1

此时 d 为[1,3,1,1,-1],求 d 的前缀和还原 a:
a [1]=1;a [2]=1+3=4;a [3]=1+3+1=5;a [4]=1+3+1+1=6;a [5]=1+3+1+1+(-1)=5(与目标一致)。

2. 二维差分

定义:对于矩阵a[1..n][1..m],其差分数组d[1..n][1..m]满足:a 是 d 的二维前缀和

核心应用:快速子矩阵更新
若需对 “左上角 (x1,y1) 到右下角 (x2,y2)” 的所有元素 “加 k”,只需操作 d 的 4 个角落:

还是建议画图+容斥原理

  • d[x1][y1] += k(起点加 k,影响整个子矩阵)
  • d[x1][y2+1] -= k(右边界减 k,抵消子矩阵外右侧的影响)
  • d[x2+1][y1] -= k(下边界减 k,抵消子矩阵外下方的影响)
  • d[x2+1][y2+1] += k(右下角补 k,抵消重复减去的部分)

最后通过 “d 的二维前缀和” 即可得到更新后的 a 矩阵。

举例
原 3x3 矩阵a全为 0(d 初始也全为 0),需求:给 (1,1)-(2,2) 的子矩阵各加 2(目标子矩阵元素为 2)。
操作 d:
d[1][1] += 2d[1][3] -= 2(y2+1=2+1=3),d[3][1] -= 2(x2+1=2+1=3),d[3][3] += 2

此时 d 的关键位置:d [1][1]=2,d [1][3]=-2,d [3][1]=-2,d [3][3]=2。
求 d 的二维前缀和还原 a:
a [1][1]=2(正确),a [1][2]=2(受 d [1][1] 影响),a [2][2]=2(正确);子矩阵外的 a [3][3] 经计算仍为 0(无影响)。

三、前缀和与差分的关系

二者是互逆操作

  • 对数组a求差分得到d,再对d求前缀和,结果是a
  • 对数组a求前缀和得到s,再对s求差分,结果是a

简单说:差分是 “前缀和的逆运算”,前缀和是 “差分的正运算”

四、适用场景总结

技巧核心作用典型场景时间复杂度优化
一维前缀和快速求子数组和频繁查询 “区间和”(如统计子数组和)查询从 O (n)→O (1)
二维前缀和快速求子矩阵和频繁查询 “子矩阵和”(如矩阵区域和)查询从 O (nm)→O (1)
一维差分快速对区间元素统一加减频繁更新 “区间元素”(如区间加 k)单次更新从 O (n)→O (1)
二维差分快速对子矩阵元素统一加减频繁更新 “子矩阵元素”(如矩阵区域加 k)单次更新从 O (nm)→O (1)

通过前缀和与差分的预处理,能将原本需要 “暴力遍历” 的操作转化为 “常数级” 的公式计算,是算法题中解决数组 / 矩阵问题的常用方法。

附一道练习题:



解决方法:

1 暴力

#include<iostream>
#include<vector>
#include <cmath>
#include <string>
#include <climits>
using namespace std;
 
 
int main() {
    int n, m;
    int suma=0, sumb=0;
    int diff = INT_MAX;
    cin >> n >> m;
    vector<vector<int>>lands(n, vector<int>(m));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> lands[i][j];
        }
    }
 
 
    int total = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            total += lands[i][j];
        }
    }
 
    //横着划分
    for (int k = 0; k < n-1; k++) {
        for (int i = 0; i <= k; i++) {
            for (int j = 0; j < m; j++) {
                suma += lands[i][j];
            }
        }
        sumb = total - suma;
        diff = abs(suma - sumb) > diff ? diff : abs(suma - sumb);
        suma = 0, sumb = 0;
    }
 
    //竖着划分
    for (int k = 0; k < m-1; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= k; j++) {
                suma += lands[i][j];
            }
        }
        sumb = total - suma;
        diff = abs(suma - sumb) > diff ? diff : abs(suma - sumb);
        suma = 0, sumb = 0;
    }
    cout << diff;
}

2. 前缀和

#include<iostream>
#include<vector>
#include <cmath>
#include <string>
#include <climits>
using namespace std;


int main() {
    int n, m;
	cin >> n >> m;
    vector<vector<int>>lands(n + 1, vector<int>(m + 1,0));
    vector<vector<int>>sum(n + 1, vector<int>(m + 1,0));
	int diff = INT_MAX;


	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> lands[i][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] + lands[i][j];
		}
	}
	int total = sum[n][m];
	//横着遍历
	for (int k = 1; k < n; k++) {
		diff = min(diff, abs(2 * sum[k][m] - total));
	}

	//竖着遍历
	for (int k = 1; k < m; k++) {
		diff = min(diff, abs(2 * sum[n][k] - total));
	}
	cout << diff;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值