揭秘前缀和的魅力:一窥算法世界中的高效工具


前言

在算法研究和编程实践中,有一种简洁而强大的工具——前缀和(Prefix Sum),能够帮助我们巧妙且高效地处理多种与数组相关的动态查询问题。今天,让我们借助一段C++代码示例,一同揭开前缀和的神秘面纱,并探讨它的实际应用。
学习算法的时候发现前缀和特别常用,也很有用,所以做此学习记录,记录自己的学习过程。


一、前缀和是什么?

在计算机科学与算法设计中,前缀和(Prefix Sum)是一个经典概念,尤其在处理数组元素累加问题时有着显著优势。前缀和的思想是:对于一个数组A = [a1, a2, …, an],它的前缀和数组S = [s1, s2, …, sn]定义为si = a1 + a2 + … + ai,即si表示数组A中前i个元素的累积和。特别地,s0通常约定为0,便于后续计算。

例如,若数组A = [1, 2, 3, 4, 5],那么对应的前缀和数组S = [0, 1, 3, 6, 10, 15]。

二、前缀和的应用场景及例题

1.应用场景

前缀和的核心价值在于,一旦预处理好前缀和数组,很多原本看似复杂的时间复杂度问题可以降为O(1),极大地提高了算法效率。以下是前缀和的一些典型应用场景:

  • 区间和查询:当我们需要频繁查询数组中任意一段连续子数组的和时,有了前缀和数组,只需计算Si - Sj - 1即可得出ai + ai+1 +
    … + aj的值。

案例中C++代码片段将会展示了这一特性,通过预先计算前缀和s[],之后可以迅速找到连续子数组的最大和。

  • 线性扫描算法优化:在图像处理、信号处理等领域,前缀和可用于快速统计滑动窗口内的像素值或信号强度总和,辅助进行滤波、峰值检测等操作。
  • 动态规划的基础工具:在一些动态规划问题中,前缀和可以作为状态转移的基础,简化状态的计算过程。
  • 在线算法:在线性扫描的过程中,利用前缀和能实时更新某一指标的累计值,无需重新遍历整个数据集。
  • 数据流处理:在大数据处理或者流式计算中,前缀和被用于实时统计流经系统的数据总量。

2.例题及分析

壁画(源于AcWing 562)
Thanh 想在一面被均分为 N
段的墙上画一幅精美的壁画。

每段墙面都有一个美观评分,这表示它的美观程度(如果它的上面有画的话)。

不幸的是,由于洪水泛滥,墙体开始崩溃,所以他需要加快他的作画进度!

每天 Thanh 可以绘制一段墙体。

在第一天,他可以自由的选择任意一段墙面进行绘制。

在接下来的每一天,他只能选择与绘制完成的墙面相邻的墙段进行作画,因为他不想分开壁画。

在每天结束时,一段未被涂颜料的墙将被摧毁(Thanh 使用的是防水涂料,因此涂漆的部分不能被破坏),且被毁掉的墙段一定只与一段还未被毁掉的墙面相邻。

Thanh 的壁画的总体美观程度将等于他作画的所有墙段的美观评分的总和。

Thanh想要保证,无论墙壁是如何被摧毁的,他都可以达到至少 B
的美观总分。

请问他能够保证达到的美观总分 B
的最大值是多少。
数据范围:
1≤T≤100,存在一个测试点N=5∗106,其他测试点均满足2≤N≤100

输入样例:

4
4
1332
4
9583
3
616
10
1029384756

输出样例:

Case #1: 6
Case #2: 14
Case #3: 7
Case #4: 31

样例解释:
在第一个样例中,无论墙壁如何被破坏,Thanh都可以获得 6
分的美观总分。在第一天,他可以随便选一个美观评分3的墙段进行绘画。在一天结束时,第一部分或第四部分将被摧毁,但无论哪一部分都无关紧要。在第二天,他都可以在另一段美观评分 3
的墙段上作画。

3.示例代码

题目等价于求长度为 (n/2)向上取整的连续子数组的最大和。

#include<iostream>
#include<algorithm>
using namespace std;

// 定义常量N以容纳较大规模的数据
const int N = 5e6 + 10;
char a[N]; // 存储原始字符型数字序列
int s[N]; // 存储前缀和序列

int main() 
{
    int T, cnt = 0; // T代表测试用例数,cnt用于记录案例序号

    // 输入测试用例总数
    scanf("%d", &T);
    
    while (T--) 
    {
        int n, i, m = 0;
        scanf("%d", &n); // 获取当前序列长度
        
        // 计算前缀和:遍历字符序列,将字符转为数字并累加到s数组
        for (i = 1; i <= n; i++) 
        {
            cin >> a[i];
            s[i] = (a[i] - '0') + s[i - 1];
        } // 这里完成了前缀和的预处理

        int k, ans = 0;
        // 根据序列长度确定连续子串的最大长度k
        if (n % 2 == 0) k = n / 2;
        else k = n / 2 + 1;

        // 利用前缀和找到满足条件的最大子串和
        for (i = k; i <= n; i++) 
            ans = max(ans, s[i] - s[i - k]);

        // 输出结果并更新案例序号
        cnt++;
        printf("Case #%d: %d\n", cnt, ans);
    }

    return 0;
}

在这段代码中,我们首先引入了iostream和algorithm库,以便进行输入输出及可能的算法操作。接下来,定义了足够大的字符数组a和整型数组s,用于存储原始数据和相应的前缀和。

在主函数main()中,我们针对每组测试用例,先计算出字符型数字序列的前缀和。前缀和简单来说就是数组中从第一个元素到当前位置的所有元素之和,对于给定序列[a1, a2, …, an],前缀和数组[s1, s2, …, sn]满足s[i] = a1 + a2 + … + ai。

随后,利用计算好的前缀和,我们可以快速求解某特定条件下连续子串的最大和,大大提升了问题解决的效率。这就是前缀和在算法实战中的魅力体现!

总结及参考来源

注意: 前缀和数组一般从1开始会比较好,因为从0开始的话,数组会越界。

总结起来,前缀和作为一种基础且强大的工具,在许多场景下都能发挥关键作用,使我们得以优雅且高效地应对各种编程挑战。下次当你面对数组类问题时,不妨思考能否运用前缀和这一技巧,也许会发现一条通往解决方案的捷径。

前缀和不仅仅是一种简单的数学概念,更是众多算法和数据结构背后的重要基石,它在实际编程问题中扮演着提升性能的关键角色。通过精心设计和有效利用前缀和,程序员们能够在各类场景中实现高效的计算和处理,彰显算法之美。

参考链接:壁画

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值