当前位置: 首页 > news >正文

一二维前缀和与差分

1.一维前缀和

1.1 一维前缀和的基本概念

前缀和是一种重要的预处理技术,常用于快速计算数组中某个区间内元素的总和。对于一个给定的数组 arr,其前缀和数组 prefix_sum 的每个元素 prefix_sum[i] 表示原数组 arr 中从索引 0 到索引 i 的所有元素的总和。

设原数组为 arr = [a₀, a₁, a₂, ..., aₙ₋₁],那么前缀和数组 prefix_sum 满足:

prefix_sum[0] = arr[0]
prefix_sum[1] = arr[0] + arr[1]
prefix_sum[2] = arr[0] + arr[1] + arr[2]
...
prefix_sum[i] = arr[0] + arr[1] + ... + arr[i]

1.2 一维前缀和的用法

前缀和的主要用途是高效地计算数组中任意区间 [left, right](其中 left 和 right 分别是区间的起始和结束索引)内元素的总和。计算区间和的公式为:

当 left > 0 时,区间 [left, right] 的和 sum = prefix_sum[right] - prefix_sum[left - 1]
当 left == 0 时,区间 [left, right] 的和 sum = prefix_sum[right]

通过使用前缀和,计算区间和的时间复杂度从直接遍历区间的O(n)降低到了O(1),其中 n 是区间的长度。

1.3 示例代码

假设有一个数组 arr = [1, 3, 5, 7, 9],来计算它的前缀和数组,并使用前缀和数组计算区间 [1, 3] 内元素的总和(即3+5+7)。

1.3.1 计算前缀和数组:

prefix_sum[0] = arr[0] = 1
prefix_sum[1] = arr[0] + arr[1] = 1 + 3 = 4
prefix_sum[2] = arr[0] + arr[1] + arr[2] = 1 + 3 + 5 = 9
prefix_sum[3] = arr[0] + arr[1] + arr[2] + arr[3] = 1 + 3 + 5 + 7 = 16
prefix_sum[4] = arr[0] + arr[1] + arr[2] + arr[3] + arr[4] = 1 + 3 + 5 + 7 + 9 = 25

所以前缀和数组 prefix_sum = [1, 4, 9, 16, 25]。
将上述代码转化为: 

prefix_sum[0] = arr[0] = 1
prefix_sum[1] = prefix_sum[0] + arr[1] = 1 + 3 = 4
prefix_sum[2] = prefix_sum[1] + arr[2] = 4 + 5 = 9
prefix_sum[3] = prefix_sum[2] + arr[3] = 9 + 7 = 16
prefix_sum[4] = prefix_sum[3] + arr[4] = 16 + 9 = 25# 可以看出prefix_sum[i] = prefix_sum[i-1] + arr[i]

前缀和数组 prefix_sum = [1, 4, 9, 16, 25]

1.3.2 计算区间内元素总和:

# 根据公式,区间 [1, 3] 的和 
sum = prefix_sum[3] - prefix_sum[1 - 1] = prefix_sum[3] - prefix_sum[0] = 16 - 1 = 15。# 根据公式,区间 [2, 3] 的和 
sum = prefix_sum[3] - prefix_sum[2 - 1] = prefix_sum[3] - prefix_sum[1] = 16 - 4 = 12。

1.3.3 完整代码

def prefix_sum(arr):n = len(arr)# 初始化前缀和数组prefix_sum = [0] * n# 计算前缀和数组的第一个元素prefix_sum[0] = arr[0]# 遍历数组,计算前缀和数组的其余元素for i in range(1, n):prefix_sum[i] = prefix_sum[i - 1] + arr[i]return prefix_sumdef range_sum(prefix_sum, left, right):if left == 0:return prefix_sum[right]else:return prefix_sum[right] - prefix_sum[left - 1]# 示例数组
arr = [1, 3, 5, 7, 9]# 计算前缀和数组
prefix_sum = prefix_sum(arr)# 计算区间 [1, 3] 内元素的总和
left, right = 1, 3
range_sum = range_sum(prefix_sum, left, right)print("原数组:", arr)
print("前缀和数组:", prefix_sum)
print(f"区间 [{left}, {right}] 内元素的总和:", range_sum)

2. 二维前缀和

2.1 二位前缀和概念

二维前缀和是一种用于高效计算二维数组中任意子矩阵元素和的数据结构,其核心思想是通过预处理数组,将子矩阵的求和操作从 O(nm) 的时间复杂度优化到 O(1)。

定义:对于一个二维数组 A(通常从索引 (1,1) 开始计算以简化边界处理),其前缀和数组 S 定义为:即 (S[i][j]) 表示从左上角 (1,1) 到 (i,j) 的矩形区域内所有元素的和。

2.2 构造方法

假设原始数组 A 的索引从 (1,1)开始,构造前缀和数组 S 的步骤如下:

初始化边界:S[0][j] = S[i][0] = 0(方便处理边界情况)。

递推公式:S[i][j] = S[i-1][j] + S[i][j-1] - S[i-1][j-1] + A[i][j]

原理

  • S[i-1][j]是左边矩形的和,S[i][j-1]是上边矩形的和。
  • 两者相加时,左上角 (1,1) 到 (i-1,j-1)的区域被重复计算了一次,因此需要减去 S[i-1][j-1]。
  • 最后加上当前元素 A[i][j]。
# 初始化矩阵
matrix = [[1,2,3],[5,6,7],[9,10,11]]# 计算二维前缀和
m, n = len(matrix), len(matrix[0])
prefix_sum = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):for j in range(1, n + 1):prefix_sum[i][j] = prefix_sum[i - 1][j] + prefix_sum[i][j - 1] - prefix_sum[i - 1][j - 1] + matrix[i-1][j-1]
print(prefix_sum)# prefix_sum=[[0, 0, 0, 0],
#             [0, 1, 3, 6],
#             [0, 6, 14, 24],
#             [0, 15, 33, 54]]

示意图:

2.3 计算区间内的元素总和

根据前缀和数组计算matrix区域(2,2)至(3,3)区域的元素和

# 定义自定义区间
prefix_sum=[[0, 0, 0, 0],[0, 1, 3, 6],[0, 6, 14, 24],[0, 15, 33, 54]]
x1, y1 = 2,2  # 左上角坐标
x2, y2 = 3,3  # 右下角坐标# 计算自定义区间的元素和
result = prefix_sum[x2][y2] - prefix_sum[x1 - 1][y2] - prefix_sum[x2][y1 - 1] + prefix_sum[x1 - 1][y1 - 1]
print(result) #34

示意图: 

3. 一维差分

3.1 一维差分的概念

一维差分是一种用于高效处理数组区间更新的数据结构,其核心思想是通过维护一个差分数组,将数组的区间操作转化为差分数组的单点操作,从而将时间复杂度从 O(n) 优化到 O(1)。

  定义

3.2 一维差分的用法

  1. 区间更新:对原始数组 A 的区间 [l, r]中的每个元素统一加上(或减去)一个值 c。
  2. 前缀查询:通过差分数组反推原始数组的前缀和(需结合前缀和算法)。

区间更新(重点)

假设我们要对原始数组 A 的区间 [l, r] 中的每个元素加上 c,可以通过以下步骤在差分数组 B 上操作:

步骤

  • B[l] += c(表示从位置 l 开始,后续元素整体增加 c)。
  • B[r+1] -= c(表示从位置 r+1 开始,抵消掉 c 的影响,确保只有 [l, r] 区间内的元素被更新)。

 因此,当 B[l] 增加 c 时,从 l 到 n 的所有 a 都会增加 c;而 B[r+1] 减去 c 则会抵消掉 r+1 到 n 的增加量,最终仅保留 [l, r] 区间的增量。

3.3 构造过程

1.初始化原数组,构造差分数组

# 初始化原数组
a=[0,1,3,5,7,9]
b=[0 for _ in range(len(a))]
b[1]=a[1]=1
b[2]=a[2]-a[1]=3-1=2
b[3]=a[3]-a[2]=5-3=2
b[4]=a[4]-a[3]=7-5=2
b[5]=a[5]-a[4]=9-7=2# 构造处差分数组b
b=[0,1,2,2,2,2]

2.区间更新操作,对原始数组 a = [1, 3, 5, 7, 9] 的区间 [2, 4] 中的每个元素加上 3,用差分数组实现。

b=[0,1,2,2,2,2]
# [2, 4]中的每个元素加上3
b[2]+=3
b[4+1]-=3b=[0, 1, 5, 2, 2, -1]

3.还原原始数组

# 还原原始数组
a[1]=b[1]=1
a[2]=a[1]+b[2]=1+5=6
a[3]=a[2]+b[3]=6+2=8
a[4]=a[3]+b[4]=8+2=10
a[5]=a[4]+b[5]=10-1=9a=[0,1,6,8,10,9]

 3.4 示例代码

3.4.1 差分数组的构造与还原

# 原始数组
arr = [1, 3, 5, 7, 9]# 构造差分数组
diff = [0] * len(arr)
diff[0] = arr[0]  # 第一个元素直接赋值
for i in range(1, len(arr)):diff[i] = arr[i] - arr[i-1]print("原始数组:", arr)
print("差分数组:", diff)  # 输出: [1, 2, 2, 2, 2]# 通过差分数组还原原始数组(前缀和操作)
restored = [0] * len(diff)
restored[0] = diff[0]
for i in range(1, len(diff)):restored[i] = restored[i-1] + diff[i]print("还原后的数组:", restored)  # 输出: [1, 3, 5, 7, 9]

3.4.2 区间更新操作示例

对原始数组的区间[2, 4](索引从 1 开始)每个元素加 3,使用差分数组实现:

# 初始化原始数组和差分数组
arr = [1, 3, 5, 7, 9]
diff = [arr[0]] + [arr[i] - arr[i-1] for i in range(1, len(arr))]# 区间更新:[2, 4] 加3(注意索引转换为0-based)
l, r, c = 2, 4, 3  # 区间[2,4],增量3
diff[l-1] += c      # 差分数组索引1(对应原数组索引2)加3
if r < len(arr):    # 避免越界diff[r] -= c# 通过差分数组还原更新后的原始数组
updated = [0] * len(diff)
updated[0] = diff[0]
for i in range(1, len(diff)):updated[i] = updated[i-1] + diff[i]print("更新后的数组:", updated)  # 输出: [1, 6, 8, 10, 9]

3.4.3 多次区间更新示例

对数组[0, 0, 0, 0, 0]进行两次区间更新:

  1. 区间[1, 3]加 2
  2. 区间[2, 5]加 3
# 初始化数组和差分数组
n = 5
arr = [0] * n
diff = [0] * n# 第一次更新:区间[1,3]加2
l1, r1, c1 = 1, 3, 2
diff[l1-1] += c1
if r1 < n:diff[r1] -= c1# 第二次更新:区间[2,5]加3
l2, r2, c2 = 2, 5, 3
diff[l2-1] += c2
# r2=5,超出数组长度(0-based索引最大为4),无需处理# 还原最终数组
final = [0] * n
final[0] = diff[0]
for i in range(1, n):final[i] = final[i-1] + diff[i]print("最终数组:", final)  # 输出: [2, 5, 5, 3, 3]

 4. 二维差分

4.1 二维差分概念

二维差分是一维差分在二维空间的扩展,用于高效处理矩阵区间更新问题。与一维差分类似,二维差分通过维护一个差分数组,将矩阵的区间操作转化为差分数组的单点操作,从而将时间复杂度从 O (nm) 优化到 O (1)。

定义:

4.2 二维差分的用法

二维差分常用于给一个矩阵的某一区域(子矩阵)统一加上(减去)同一个值v

使用差分可以将在数组 a 上的区块操作转化为在差分数组 b 上的四个单点操作

子矩阵左上角坐标为 (x1, y1),右下角坐标为 (x2, y2),对该区块中的每个元素都加上 v 等价于下面四个操作:(图片来自于二维差分详解与应用-CSDN博客)

1.  

2.  

 

3.  

 4. 

 

4.3 计算二维差分数组

对原矩阵 A 的子矩阵 [(x1,y1), (x2,y2)] 内所有元素加上常数 c,只需在差分数组 B 上执行以下操作:

  • B[x1][y1] += c
  • B[x1][y2+1] -= c
  • B[x2+1][y1] -= c
  • B[x2+1][y2+1] += c

 4.4 示例代码

问题:给定原始数组a,要求在其[(0,0),(1,1)]子矩阵上全部+1,[(1,1),(2,2)]子矩阵上全部-2

4.4.1 初始化数组及变量

a=[[1,2,0],[0,2,3],[1,3,2]]
n=3
diff = [[0] * (n + 2) for _ in range(n + 2)]
move=[[0,1,0,1],   # [(0,0),(1,1)]子矩阵[1,2,1,2]]   # [(1,1),(2,2)]子矩阵
change=[1,-2]      # 改变量分别为1,-2

4.4.2 二维差分数组单点操作

for i in range(2):x1,x2,y1,y2=move[i]x1+=1;x2+=1;y1+=1;y2+=1   (转变索引方式)diff[x1][y1]+=change[i]diff[x1][y2+1]-=change[i]diff[x2+1][y1]-=change[i]diff[x2+1][y2+1]+=change[i]
#     print(diff)
# diff=[[0, 0, 0, 0, 0],
#       [0, 1, 0, -1, 0],
#       [0, 0, -2, 0, 2],
#       [0, -1, 0, 1, 0],
#       [0, 0, 2, 0, -2]]

4.4.3 二维差分数组求前缀和

for i in range(1,n+2):for j in range(1,n+2):diff[i][j]+=diff[i-1][j]+diff[i][j-1]-diff[i-1][j-1]
# diff=[[0, 0, 0, 0, 0],
#      [0, 1, 1, 0, 0],
#      [0, 1, -1, -2, 0],
#      [0, 0, -2, -2, 0],
#      [0, 0, 0, 0, 0]]

当计算当这里显然可以看出前缀和diff 符合在其[(0,0),(1,1)]子矩阵上全部+1,[(1,1),(2,2)]子矩阵上全部-2, 我们只需将原数组a映射到diff 数组的中间区域即可

4.4.4 原数组映射

for i in range(n):for j in range(n):a[i][j]+=diff[i+1][j+1]
print(a)
# a=[[2, 3, 0], 
#    [1, 1, 1], 
#    [1, 1, 0]]

4.5 蓝桥杯真题--棋盘

利用二维差分,轻松通过全部案例

n, m = map(int, input().split())
# 初始化一个全为0的(n + 1) x (n + 1)的二维数组来表示差分数组
diff = [[0] * (n + 2) for _ in range(n + 2)]# 执行m次操作
for _ in range(m):x1, y1, x2, y2 = map(int, input().split())# 更新差分数组的操作范围diff[x1][y1] += 1diff[x1][y2 + 1] -= 1diff[x2 + 1][y1] -= 1diff[x2 + 1][y2 + 1] += 1# 计算最终棋盘状态
for i in range(1, n + 1):for j in range(1, n + 1):diff[i][j] += diff[i][j - 1] + diff[i - 1][j] - diff[i - 1][j - 1]# 棋盘上该位置为偶数时为0,奇数时为1print(1 if diff[i][j] % 2 == 1 else 0, end='')print()

 

相关文章:

  • 十二、Hive 函数
  • 文件转Markdown工具有哪些
  • JavaScript入门【3】面向对象
  • 【第一篇】 创建SpringBoot工程的四种方式
  • 【以及好久没上号的闲聊】Unity记录8.1-地图-重构与优化
  • 当硅基存在成为人性延伸的注脚:论情感科技重构社会联结的可能性
  • JVM 机制
  • 【论文阅读】人脸修复(face restoration ) 不同先验代表算法整理
  • Adobe Illustrator学习备忘
  • 单细胞转录组(4)Cell Ranger
  • 项目管理学习-CSPM-4考试总结
  • vscode用python开发maya联动调试设置
  • Redis 数据类型与操作完全指南
  • 开源语音-文本基础模型和全双工语音对话框架 Moshi 介绍
  • 【Redis】List 列表
  • 谈谈未来iOS越狱或巨魔是否会消失
  • Redis的Hot Key自动发现与处理方案?Redis大Key(Big Key)的优化策略?Redis内存碎片率高的原因及解决方案?
  • 计算机网络(1)——概述
  • Redis——缓存雪崩、击穿、穿透
  • WSL 安装 Debian 12 后,如何安装图形界面 X11 ?
  • 辽宁援疆前指总指挥王敬华已任新疆塔城地委副书记
  • 会谈时间迟迟未定、核心议题存在分歧,俄乌“土耳其谈判”一波三折
  • 央视起底“字画竞拍”网络传销案:涉案44亿元,受害者众多
  • 九江宜春领导干部任前公示,3人拟提名为县(市、区)长候选人
  • 浙能集团原董事长童亚辉被查,还是杭州市书法家协会主席
  • 山东市监局回应“盒马一批次‘无抗’鸡蛋抽检不合格后复检合格”:系生产商自行送检