注:本文是在学习了acwing的算法基础课后撰写,主要用于记录python版本算法的模板。其中部分参考了acwing众多大佬的题解。
思想:
原数组 a 1 a 2 . . . a n a_1 a_2...a_n a1a2...an
前缀和 S i = a 1 + a 2 + . . . + a i S_i = a_1 + a_2 + ... + a_i Si=a1+a2+...+ai (下标从1开始方便计算),定义 S 0 = 0 S_0 = 0 S0=0
前缀和的作用:快速求出原数组中一段数的和。
例如,求 [l, r] 区间内数组a的和,不需要遍历a [l, r]部分,只需要 S r − S l − 1 S_r - S_{l-1} Sr−Sl−1即可。也就是说计算的时间从O(n)降为了O(1),但计算前缀和数组需要O(n)的时间。因此只求一段数的和实际上没必要使用计算前缀和,但当我们要计算多段时,前缀和就可以节省时间。
步骤:
1.求前缀和 S i = a 1 + a 2 + . . . + a i S_i = a_1 + a_2 + ... + a_i Si=a1+a2+...+ai
2.对前缀和做减法,求部分和 a l + . . . + a r = S r − S l − 1 a_l + ... + a_r = S_r - S_{l-1} al+...+ar=Sr−Sl−1
模板:
if __name__ == "__main__":
n, m = map(int, input().split())
nums = list(map(int, input().split()))
prefix = [0] * (n + 10) # 前缀和数组
for i in range(n):
prefix[i+1] = prefix[i] + nums[i] # 求前缀和
for i in range(m):
l, r = map(int, input().split())
print(prefix[r] - prefix[l-1]) # 求部分和
思想:
已知矩阵,快速求子矩阵和。思想基本与一维一致。
上图源于acwing796题题解
不记得公式时,可以绘制类似上图的表格推公式。
步骤:
1.求前缀和 S i j = S ( i − 1 ) j − S i ( j − 1 ) − S ( i − 1 ) ( j − 1 ) + a i j S_{ij} = S_{(i-1)j} - S_{i(j-1)} - S_{(i-1)(j-1)} + a_{ij} Sij=S(i−1)j−Si(j−1)−S(i−1)(j−1)+aij
2.求部分和 = S x 2 y 2 − S x 2 ( y 1 − 1 ) − S ( x 1 − 1 ) y 2 + S ( x 1 − 1 ) ( y 1 − 1 ) =S_{x_2y_2} - S_{x_2(y_1-1)} - S_{(x_1-1)y_2} + S_{(x_1-1)(y_1-1)} =Sx2y2−Sx2(y1−1)−S(x1−1)y2+S(x1−1)(y1−1)
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和
模板:
if __name__ == "__main__":
n, m, q = map(int, input().split())
nums = []
for _ in range(n):
nums.append([int(i) for i in input().split()])
# 1.计算前缀和
s = [[0]*(m+1) for _ in range(n+1)]
for i in range(1, n+1):
for j in range(1, m+1):
s[i][j] = nums[i-1][j-1] + s[i-1][j] + s[i][j-1] - s[i-1][j-1]
ans = []
# 2.计算区域和
for _ in range(q):
x1, y1, x2, y2 = map(int, input().split())
ans.append(s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1])
print('\n'.join(map(str, ans)))
思想:
差分是前缀和的逆运算。是已知前缀和数组推原数组的过程。
即已知 a 1 a 2 . . . a i a_1a_2...a_i a1a2...ai,构造 b 1 b 2 . . . b i b_1b_2...b_i b1b2...bi,使得 a i = b 1 + b 2 + . . . + b i a_i = b_1 + b_2 + ... + b_i ai=b1+b2+...+bi。
差分的作用:若要修改 a l . . . a r a_l...a_r al...ar内的元素+c,只需将 b l b_l bl+c, b r + 1 b_{r+1} br+1-c。将这步操作称为insert,insert函数就是差分的核心。
构建初始差分数组也可以通过insert完成。具体可见代码。
步骤:
1.构建初始差分数组b(遍历数组a,对数组b在i位置插a[i])
2.根据题意修改差分数组
3.利用修改后的差分数组重新计算前缀和
模板:
# 核心
def insert(b, l, r, c):
b[l] += c
b[r+1] -= c
if __name__ == "__main__":
n, m = map(int, input().split())
a = [0] * (n + 10) # 原数组
b = [0] * (n + 10) # 差分数组
nums = list(map(int, input().split()))
for index, val in enumerate(nums):
a[index+1] = val
# 构建初始差分数组b[]
for i in range(1, n+1):
insert(b, i, i, a[i])
# 根据题意修改差分数组
while m > 0:
m -= 1
l, r, c = map(int, input().split())
insert(b, l, r, c)
# 利用修改后的差分数组重新计算前缀和
for i in range(1, n+1):
b[i] += b[i-1]
for i in range(1, n+1):
print(b[i], end=" ")
思想:
与一维基本一致,重点关注insert部分的不同。
若要对原数组a修改以(x1, y1)为左上角,(x2, y2)为右下角的矩阵块。需要对差分数组b做如下步骤:
b[x1][y1] += c
b[x2+1][y1] -= c
b[x1][y2+1] -= c
b[x2+1][y2+1] += c
步骤:
与一维一致。
1.构建初始差分数组b
2.根据题意修改差分数组
3.利用修改后的差分数组重新计算前缀和
模板:
# 核心
def insert(b, x1, y1, x2, y2, c):
b[x1][y1] += c
b[x2+1][y1] -= c
b[x1][y2+1] -= c
b[x2+1][y2+1] += c
if __name__ == "__main__":
m, n, q = map(int, input().split())
a = [[0 for _ in range(n+10)] for _ in range(m+10)]
b = [[0 for _ in range(n+10)] for _ in range(m+10)]
for i in range(1, m+1):
nums = list(map(int, input().split()))
for j, val in enumerate(nums):
a[i][j+1] = val
# 构建初始差分数组b[][]
for i in range(1, m+1):
for j in range(1, n+1):
insert(b, i, j, i, j, a[i][j])
# 根据题意修改差分数组
while q > 0:
q -= 1
x1, y1, x2, y2, c = map(int, input().split())
insert(b, x1, y1, x2, y2, c)
# 利用修改后的差分数组重新计算前缀和
for i in range(1, m+1):
for j in range(1, n+1):
a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + b[i][j]
for i in range(1, m+1):
for j in range(1, n+1):
print(a[i][j], end=" ")
print()