- 前缀和算法介绍
- 前缀和实例
- 实例思路
- 代码
1、前缀和算法介绍
1.1概念
前缀和算法(Prefix Sum Algorithm)是一种通过预先计算和存储数组前缀和来加速子数组求和操作的方法。它可以用于高效地回答和大量子数组求和相关的查询。
1.2算法原理
-
计算前缀和:给定一个数组a,我们可以通过以下方式计算出其前缀和数组prefix_sum:
- prefix_sum[0] = a[0]
- prefix_sum[1] = a[0] + a[1]
- prefix_sum[2] = a[0] + a[1] + a[2]
- ...
- prefix_sum[i] = a[0] + a[1] + ... + a[i]
-
利用前缀和求子数组和:如果我们想要计算数组a中下标l到下标r之间所有元素的和,我们可以使用前缀和数组进行快速计算:
子数组和 = prefix_sum[r] - prefix_sum[l-1] (这里需要对边界情况进行特殊处理)
1.3应用
前缀和算法在解决很多子数组求和相关的问题时非常高效,比如连续子数组的最大和、子数组的均值等问题。
2.前缀和实例——壁画
2.1问题描述
Thanh 想在一面被均分为 N 段的墙上画一幅精美的壁画。每段墙面都有一个美观评分,这表示它的美观程度(如果它的上面有画的话)。不幸的是,由于洪水泛滥,墙体开始崩溃,所以他需要加快他的作画进度!每天 Thanh 可以绘制一段墙体。在第一天,他可以自由的选择任意一段墙面进行绘制。在接下来的每一天,他只能选择与绘制完成的墙面相邻的墙段进行作画,因为他不想分开壁画。在每天结束时,一段未被涂颜料的墙将被摧毁(Thanh 使用的是防水涂料,因此涂漆的部分不能被破坏),且被毁掉的墙段一定只与一段还未被毁掉的墙面相邻。Thanh 的壁画的总体美观程度将等于他作画的所有墙段的美观评分的总和。Thanh想要保证,无论墙壁是如何被摧毁的,他都可以达到至少 B的美观总分。请问他能够保证达到的美观总分 B 的最大值是多少。
2.2输入格式
第一行包含整数 T,表示共有 T组测试数据。
每组数据的第一行包含整数 N。
第二行包含一个长度为 N 的字符串,字符串由数字 0∼9 构成,第 i个字符表示第 i段墙面被上色后能达到的美观评分。
2.3输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为 Case #x: y
,其中 x为组别编号(从 1开始),y 为 Thanh 可以保证达到的美观评分的最大值。
2.4数据范围
1≤T≤100,
存在一个测试点N=5∗,其他测试点均满足2≤N≤100
2.5输入样例
4
4
1332
4
9583
3
616
10
1029384756
2.6输出样例
Case #1: 6
Case #2: 14
Case #3: 7
Case #4: 31
3.实例思路
首先,理解题意。
有一段墙壁,其共有N面墙壁,每一面墙壁上均可作画。我们的主角Thanh每一天可以画一面墙壁,但是潮水每天在主角作画后,同样会破坏一面墙壁(注意,其只能从两侧开始破坏,随机破坏左边或者右边)。且主角为了壁画的整体美观,除了第一天可以任意选取一面墙壁作画外,第二天开始必须在已经作画的壁画两侧选择一面墙壁作画,每一面壁画作画后都有一个具体数值,求壁画完成后,能够得到的最高分数值是多少。
然后,具体分析。
第一,我们的主角壁画完成后,其作画的墙壁数为多少?
由题意知,潮水破坏的一定是两侧的墙壁,那么,如果给出的墙壁数为偶数(N)个,那么最好的情况一定是主角完成了一半墙壁的作画(N/2),潮水破坏了一半墙壁(N/2);如果如果给出的墙壁数为奇数(N)个,那么最好的情况一定是主角完成了一半墙壁的作画(N/2向上取整),潮水破坏了一半墙壁(N/2向下取整)。
第二,得到作画的墙壁数以后,如何得到具体那些墙壁作画呢,如何得到最高分数?
由于题目只需要得到最高分数,那么与具体每天的作画的墙壁顺序并没有关系,那么,我们只需要统计在一段长度为N的数组中,其长度为N/2(或N/2向上取整)的数组的求和的最大值即可。
4.代码
4.1未使用前缀和算法代码
import time
def straw_wall(n1, n2, n3):
for i in range(n1): # 测试组数
a = 0
b = 0
num2 = 0
if n2[i] % 2 == 0:
a = int(n2[i] / 2 + 1)
for _ in range(a):
num1 = 0
for w in range(b, b + a - 1):
num1 += n3[i][w]
num2 = max(num1, num2)
b += 1
i = i + 1
print("Case #" + str(i) + ": " + str(num2))
else:
a = int((n2[i] + 1) / 2)
for _ in range(a):
num1 = 0
for w in range(b, b + a):
num1 += n3[i][w]
num2 = max(num1, num2)
b += 1
i = i + 1
print("Case #" + str(i) + ": " + str(num2))
if __name__ == "__main__":
num1 = int(input())
num2 = [] # 输入墙壁数N
num3 = [] # 墙壁分值
for _ in range(num1):
num2.append(int(input()))
num3.append(list(int(x) for x in input()))
start1 = time.time()
straw_wall(num1, num2, num3)
end1 = time.time()
print(end1-start1)
其运行时间统计为(输入10组数据):
Case #1: 6
Case #2: 14
Case #3: 7
Case #4: 31
Case #5: 183
Case #6: 250
Case #7: 231
Case #8: 408
Case #9: 261
Case #10: 379
0.0010004043579101562
进程已结束,退出代码 0
4.2使用前缀和算法代码
import time
# 优化
def straw_wall(num_cases, wall_lengths, wall_scores):
for case in range(num_cases):
max_sum = 0
length = wall_lengths[case]
scores = wall_scores[case]
if length % 2 == 0:
half_length = length // 2
current_sum = sum(scores[:half_length])
max_sum = max(max_sum, current_sum)
for i in range(half_length, length):
current_sum += scores[i] - scores[i - half_length]
max_sum = max(max_sum, current_sum)
else:
half_length = (length + 1) // 2
current_sum = sum(scores[:half_length])
max_sum = max(max_sum, current_sum)
for i in range(half_length, length):
current_sum += scores[i] - scores[i - half_length]
max_sum = max(max_sum, current_sum)
print(f"Case #{case + 1}: {max_sum}")
if __name__ == "__main__":
num_cases = int(input())
wall_lengths = []
wall_scores = []
for _ in range(num_cases):
wall_lengths.append(int(input()))
wall_scores.append(list(int(x) for x in input()))
start1 = time.time()
straw_wall(num_cases, wall_lengths, wall_scores)
end1 = time.time()
print(end1-start1)
其运行时间统计为(输入10组数据):
Case #1: 6
Case #2: 14
Case #3: 7
Case #4: 31
Case #5: 183
Case #6: 250
Case #7: 231
Case #8: 408
Case #9: 261
Case #10: 379
0.0009961128234863281
比较可知,使用前缀和的时间复杂度更低,当然测试组数越多,可以发现使用前缀和的效果越好。