欢迎光临
我们一直在努力

第一章 前缀和算法&差分算法

1. 前缀和算法&差分算法

本章将详细讲解前缀和与差分两种基础核心算法的核心定义、原理、使用场景及代码模板,两种算法是算法竞赛中高频出现的基础优化手段,适配区间操作类题型。本章所有代码案例已统一收录至代码仓库。代码仓库链接

1.1 算法核心定义与原理

1.1.1 前缀和算法

前缀和算法是区间求和专用的高效预处理优化算法,核心作用是将多次区间求和的时间复杂度大幅降低,完美解决大数据量区间求和超时问题。

在未优化的暴力解法中,若数组长度为 x x x,存在 m m m 次区间查询,单次查询遍历区间长度为 n n n,整体时间复杂度为 O ( m ⋅ n ) O(m \\cdot n) O(mn)。当数据量达到 10 5 10^5 105 级别时,暴力解法会直接超时。

而前缀和算法通过一次预处理、多次查询的思路,预处理时间复杂度为 O ( x ) O(x) O(x),单次区间查询仅为 O ( 1 ) O(1) O(1),海量查询场景下效率碾压暴力解法,是算法竞赛区间求和题型的必备技巧。

算法核心简述

定义原数组为 a a a(本文统一采用0下标存储),新建前缀和数组 p r e pre pre。其中 p r e [ i ] pre[i] pre[i] 表示:原数组 a a a 中前 i + 1 i+1 i+1 个元素的总和,即 a [ 0 ] a[0] a[0] a [ i ] a[i] a[i] 的累加和。

前缀和数组递推规律如下:

p r e [ 0 ] = a [ 0 ] p r e [ 1 ] = a [ 0 ] + a [ 1 ] p r e [ 2 ] = a [ 0 ] + a [ 1 ] + a [ 2 ] ⋯ p r e [ n ] = ∑ k = 0 n a [ k ] pre[0] = a[0] \\\\ pre[1] = a[0]+a[1] \\\\ pre[2] = a[0]+a[1]+a[2] \\\\ \\cdots \\\\ pre[n] = \\sum_{k=0}^n a[k] pre[0]=a[0]pre[1]=a[0]+a[1]pre[2]=a[0]+a[1]+a[2]pre[n]=k=0na[k]

通过递推公式可快速推导:原数组任意区间 (0下标,包含两端)的元素和 = p r e [ r ] − p r e [ l − 1 ] pre[r] – pre[l-1] pre[r]pre[l1] 。特殊地,当 l = 0 l=0 l=0 时,区间和直接等于 p r e [ r ] pre[r] pre[r]

经典模板题

题目描述:给定长度为 x x x 的数组 a a a,以及 n n n 次区间查询,每次给出区间 [ l , r ] [l, r] [l,r],求数组第 l l l 项到第 r r r 项的区间和,逐行输出每次查询结果。

输入格式:

第一行: x , n x, n x,n(数组长度、查询次数)

第二行: a 1 , a 2 , … , a x a_1, a_2, \\dots, a_x a1,a2,,ax(数组元素)

后续 n n n 行:每行两个数 l i , r i l_i, r_i li,ri(查询区间)

数据范围: 0 ≤ l ≤ r ≤ x ≤ 10 5 0 \\le l \\le r \\le x \\le 10^5 0lrx105 0 < n ≤ 10 5 0 \\lt n \\le 10^5 0<n105

代码模板

代码路径:1/prefix_sum.cpp

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;

int a[maxn], pre[maxn];

int main()
{


ios::sync_with_stdio(false);
cin.tie(nullptr);

int x, n;
cin >> x >> n;

// 读入原数组
for (int i = 0; i < x; i++)
{


cin >> a[i];
}

// 预处理前缀和数组
pre[0] = a[0];
for (int i = 1; i < x; i++)
{


pre[i] = pre[i 1] + a[i];
}

// 处理n次区间查询
int l, r;
for (int i = 0; i < n; i++)
{


cin >> l >> r;
// 适配0下标区间求和公式
if (l == 0) cout << pre[r] << \”\\n\”;
else cout << pre[r] pre[l 1] << \”\\n\”;
}
return 0;
}

1.1.2 差分算法

差分算法是区间批量修改专用的高效预处理算法,核心场景为:对数组多次执行「区间统一加/减固定数值」的操作,最后输出修改后的完整数组。

暴力区间修改的时间复杂度为 O ( m ⋅ n ) O(m \\cdot n) O(mn),大数据量下极易超时;差分算法通过预处理差分数组,将整体复杂度优化至 O ( x + q ) O(x+q) O(x+q),是区间批量更新题型的最优解。

算法核心简述

差分是前缀和的逆运算。基于原数组 a a a(0下标)构建差分数组 d i f f diff diff,数组元素对应规则如下:

d i f f [ 0 ] = a [ 0 ] d i f f [ 1 ] = a [ 1 ] − a [ 0 ] d i f f [ 2 ] = a [ 2 ] − a [ 1 ] ⋯ d i f f [ i ] = a [ i ] − a [ i − 1 ] ( i ≥ 1 ) diff[0] = a[0] \\\\ diff[1] = a[1] – a[0] \\\\ diff[2] = a[2] – a[1] \\\\ \\cdots \\\\ diff[i] = a[i] – a[i-1] \\quad (i \\ge 1) diff[0]=a[0]diff[1]=a[1]a[0]diff[2]=a[2]a[1]diff[i]=a[i]a[i1](i1)

核心操作原理:对原数组区间 [ l , r ] [l, r] [l,r] 统一增加数值 q q q,仅需对差分数组执行两步操作:

  • d i f f [ l ] + = q diff[l] += q diff[l]+=q(区间起点生效,后续所有元素累加q)

  • d i f f [ r + 1 ] − = q diff[r+1] -= q diff[r+1]=q(区间终点后一位抵消,保证区间外元素不受影响)

  • 所有区间修改完成后,对 d i f f diff diff 数组求一次前缀和,即可还原得到修改后的原数组。

    手动推演示例

    设原数组 a = { 1 , 5 , 3 , 7 , 8 , 2 , 4 } a = \\{1,5,3,7,8,2,4\\} a={
    1,5,3,7,8,2,4}
    ,构建初始差分数组: d i f f = { 1 , 4 , − 2 , 4 , 1 , − 6 , 2 } diff = \\{1,4,-2,4,1,-6,2\\} diff={
    1,4,2,4,1,6,2}

    执行操作:对区间 [ 2 , 4 ] [2,4] [2,4](0下标)所有元素加3。

  • 差分更新: d i f f [ 2 ] + = 3 diff[2] += 3 diff[2]+=3 d i f f [ 5 ] − = 3 diff[5] -= 3 diff[5]=3,更新后 d i f f = { 1 , 4 , 1 , 4 , 1 , − 9 , 2 } diff = \\{1,4,1,4,1,-9,2\\} diff={
    1,4,1,4,1,9,2}

  • 前缀和还原数组:得到新数组 a = { 1 , 5 , 6 , 10 , 11 , 2 , 4 } a = \\{1,5,6,10,11,2,4\\} a={
    1,5,6,10,11,2,4}

  • 效果验证:仅区间 [ 2 , 4 ] [2,4] [2,4] 元素全部+3,其余元素保持不变,符合预期。

  • 经典模板题

    题目描述:给定长度为 x x x 的数组 a a a,进行 n n n 次区间修改操作,每次操作将区间 [ l , r ] [l, r] [l,r] 内所有元素增加数值 q q q,最终输出修改后的完整数组。

    输入格式:

    第一行: x , n x, n x,n

    第二行: a 1 , a 2 , … , a x a_1, a_2, \\dots, a_x a1,a2,,ax

    后续 n n n 行:每行三个数 l i , r i , q i l_i, r_i, q_i li,ri,qi

    数据范围:同前缀和模板题,所有数据保证在 int 范围内。

    代码模板(优化版)

    代码路径:1/difference.cpp

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5 + 5;

    int a[maxn], diff[maxn];

    int main()
    {


    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int x, n;
    cin >> x >> n;

    // 读入原数组
    for (int i = 0; i < x; i++)
    {


    cin >> a[i];
    }

    // 构建差分数组
    diff[0] = a[0];
    for (int i = 1; i < x; i++)
    {


    diff[i] = a[i] a[i 1];
    }

    // 批量处理区间修改操作
    for (int i = 0; i < n; i++)
    {


    int l, r, q;
    cin >> l >> r >> q;
    // 转换为0下标
    l, r;
    diff[l] += q;
    // r+1越界时无需抵消(无后续元素,不影响结果)
    if (r + 1 < x)
    diff[r + 1] -= q;
    }

    // 前缀和还原修改后的数组
    for (int i = 1; i < x; i++)
    {


    diff[i] += diff[i 1];
    }

    // 输出结果
    for (int i = 0; i < x; i++)
    {


    cout << diff[i] << \” \”;
    }
    return 0;
    }

    1.1.3 算法小结

    前缀和与差分是算法竞赛入门最基础、最实用的成对算法,二者互为逆运算,核心价值均为优化区间操作时间复杂度,彻底解决暴力解法的超时问题。

  • 前缀和:专攻多查询、无修改的区间求和场景,一次预处理,O(1)快速查询;

  • 差分:专攻多修改、最后查询的区间批量更新场景,一次预处理,高效完成批量修改;

  • 两种算法逻辑简洁、代码量小,是后续进阶算法(二维前缀和、差分约束等)的基础,必须熟练掌握模板与核心原理。

    1.2 算法进阶应用

    1.2.1 二维前缀和算法

    二维前缀和与一维前缀和核心思想完全一致,均通过预处理实现区间查询 O ( 1 ) O(1) O(1),只是维度上升后需要借助容斥原理进行计算,逻辑更加复杂。

    二维前缀和主要用于快速求取子矩阵元素和。给定大小为 n × m n \\times m n×m的矩阵,共 q q q次子矩阵查询。暴力做法需要遍历整个子矩阵,时间复杂度 O ( q ⋅ S ) O(q\\cdot S) O(qS) S S S为子矩阵面积);二维前缀和预处理复杂度 O ( n m ) O(nm) O(nm),单次查询仅 O ( 1 ) O(1) O(1)。当矩阵规模、查询次数较大时,效率优势十分明显。

    算法核心简述

    定义二维前缀和数组 p r e [ i ] [ j ] pre[i][j] pre[i][j]:代表原矩阵左上角 ( 1 , 1 ) (1,1) (1,1) ( i , j ) (i,j) (i,j)围成矩形内所有元素之和。 p r e [ i ] [ j ] = ∑ x = 1 i ∑ y = 1 j a [ x ] [ y ] pre[i][j]=\\sum_{x=1}^{i}\\sum_{y=1}^{j} a[x][y] pre[i][j]=x=1iy=1ja[x][y]

    构建二维前缀和依赖容斥原理:

    • p r e [ i ] [ j ] pre[i][j] pre[i][j] = 当前位置原元素 a [ i ] [ j ] a[i][j] a[i][j]
    • 上方矩形和 p r e [ i − 1 ] [ j ] pre[i-1][j] pre[i1][j]
    • 左侧矩形和 p r e [ i ] [ j − 1 ] pre[i][j-1] pre[i][j1]
    • 左上角重复计算的矩形和 p r e [ i − 1 ] [ j − 1 ] pre[i-1][j-1] pre[i1][j1]

    公式: p r e [ i ] [ j ] = a [ i ] [ j ] + p r e [ i − 1 ] [ j ] + p r e [ i ] [ j − 1 ] − p r e [ i − 1 ] [ j − 1 ] pre[i][j] = a[i][j] + pre[i-1][j] + pre[i][j-1] – pre[i-1][j-1] pre[i][j]=a[i][j]+pre[i1][j]+pre[i][j1]pre[i1][j1]

    完成预处理后,设目标子矩阵左上角 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),右下角 ( x 2 , y 2 ) (x_2,y_2) (x2,y2),子矩阵和公式: s u m = p r e [ x 2 ] [ y 2 ] − p r e [ x 1 − 1 ] [ y 2 ] − p r e [ x 2 ] [ y 1 − 1 ] + p r e [ x 1 − 1 ] [ y 1 − 1 ] sum = pre[x_2][y_2] – pre[x_1-1][y_2] – pre[x_2][y_1-1] + pre[x_1-1][y_1-1] sum=pre[x2][y2]pre[x11][y2]pre[x2][y11]+pre[x11][y11]

    原理:

  • p r e [ x 2 ] [ y 2 ] pre[x_2][y_2] pre[x2][y2]:整个大矩形总和
  • 减去上方多余区域 p r e [ x 1 − 1 ] [ y 2 ] pre[x_1-1][y_2] pre[x11][y2]
  • 减去左侧多余区域 p r e [ x 2 ] [ y 1 − 1 ] pre[x_2][y_1-1] pre[x2][y11]
  • 上方、左侧重叠区域被连续减去两次,需要加回一次 p r e [ x 1 − 1 ] [ y 1 − 1 ] pre[x_1-1][y_1-1] pre[x11][y11]
  • 经典模板题

    题目描述:给定一个 n n n m m m 列的整数矩阵,一共有 q q q 次询问。每次询问给出四个整数 x 1 , y 1 , x 2 , y 2 x_1,y_1,x_2,y_2 x1,y1,x2,y2,求矩阵中第 x 1 x_1 x1 行至第 x 2 x_2 x2 行、第 y 1 y_1 y1 列至第 y 2 y_2 y2 列围成矩形内所有数字的总和,分行输出。

    输入格式 第一行三个整数 n , m , q n,m,q n,m,q,代表矩阵行数、列数、询问次数。 接下来 n n n 行,每行 m m m 个整数,表示矩阵元素。 接下来 q q q 行,每行四个整数 x 1 , y 1 , x 2 , y 2 x_1,y_1,x_2,y_2 x1,y1,x2,y2,保证 1 ≤ x 1 ≤ x 2 ≤ n ,   1 ≤ y 1 ≤ y 2 ≤ m 1\\le x_1\\le x_2 \\le n,\\ 1\\le y_1\\le y_2 \\le m 1x1x2n, 1y1y

    赞(0)
    未经允许不得转载:171主机测评 » 第一章 前缀和算法&差分算法
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址