zoukankan      html  css  js  c++  java
  • [知识点] 2.7 前缀和与差分

    总目录 > 2 算法基础 > 2.7 前缀和与差分

    前言

    没有想到前缀和也被单独拿出来作为一节来讲,不过也好,还可以顺便讲讲前面又碰到了一次的多维前缀和以及差分。

    子目录列表

    1、前缀和

    2、差分

    3、多维前缀和

    2.7 前缀和与差分

    1、前缀和

    前缀和:对于数列 a,其第 1, 2, ..., i 项之和,即 a[1] + a[2] + ... + a[i],称为数列 a 第 i 项的前缀和

    学过高中数学的数列章节就知道,“前缀和”和“前 n 项和 Sn”的概念是等价的。

    前缀和有啥用?最简单的,如果我们要求数列某一个区间 [l, r] 之和,如果预处理了前缀和 sum,则直接 sum[r] - sum[l - 1] 就得到了区间和。

    2、差分

    和前缀和相对的一个概念。

    差分:对于数列 a,其第 i 个元素和第 i - 1 个元素的差称为数列 a 第 i 项的差分

    令差分数组为 b,则存在:b[i] = a[i] - a[i - 1]

    有意思的是,对差分数组求前缀和就又可以得到原数列 a 了。

    sumb[i] = b[1] + b[2] + ... b[i] = a[1] + a[2] - a[1] + ... + a[i] - a[i - 1] = a[i]

    那么我们拿着这个差分数组有什么用捏?

    【例题】给定一个数列 a,进行 m 次操作,每次给定三个值 l, r, p,操作类型如下:

    ① 1 l r p,表示对 a[l..r] 的每一个元素加上 p;

    ② 2 l r,表示查询 a[l..r] 的元素和。

    确保所有操作 ① 执行完后才会有操作 ②

    最简单的做法就是对于修改操作逐一加上 p,对于查询操作可以用前面的前缀和来求。但注意到题目最后这个限定:所有修改操作会在任意查询操作之前执行,这样其实我们可以拿差分数组做文章,每次只修改差分数组的两个端点即可,见下面的图解举例:

    是不是很神奇呢?修改的方式很简单,如果修改区间为 [l, r],则对 b[l] += p, b[r + 1] -=p,具体就不解释原因了。 

    不过限定条件也体现出了这种方式的不足:差分数组相当于提供了一个临时的方舱医院,需要时间搭建,但一旦搭建好了就能很快收容大量病人,而等疫情结束后再拆除;普通医院是现成的,但床位不够效率不高,然而适用于平常的各种疑难杂症,可以随时收治(好像也不是很恰当、)。

    所以如果遇到修改操作和查询操作交替出现的情况,差分数组的便捷则完全体现不出,需要反反复复地在原数组和差分数组之间转化,那和直接枚举的效率不相上下。但同时出现两种操作显然是更符合现实情况的,而要解决这种问题,树状数组(请参见:<施工中>)和线段树(请参见:<施工中>)都是很棒的方法,它们适用范围更广于差分数组,但是搭建起来,尤其是线段树,则较为麻烦。

    3、多维前缀和

    ① 二维前缀和

    上述前缀和为一维线性的,而对于矩阵二维数组,则:

    令原矩阵为 a,定义另一个矩阵 sum,有

     

    即矩阵 a 的第 i 行第 j 列的前缀和为 sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j]。

    如图:

    二维前缀和又有什么用呢?下面给出一道例题:

    Luogu P1387】【最大正方形】在一个 n * m 的 0 - 1 矩阵里找到一个不包括 0 的最大正方形并输出其边长。

    如图所示,首先按上述过程求出二维前缀和。

    随便指定两个坐标 (2, 2), (3, 3),由于前缀和全部都是以 (1, 1) 为左上端点,所以对于这种不搭边的正方形同样需要转化为以 (1, 1) 为左上端点的正方形。

    根据容斥原理,不难发现以其为两个端点组成的正方形是通过这三部分加减而成:3 * 3 的正方形,减去左侧 3 * 1 的矩形,再减去上方 1 * 3 的矩形,最后再补上被减去两次的 1 * 1。

    而因为有二维前缀和,求和也就根据这个思路:ans = sum[3][3] - sum[3][1] - sum[1][3] + sum[1][1] = 7 - 1 - 2 + 0 = 4

    这样,我们就可以通过枚举两个端点坐标,同时以 O(1) 的效率求出由两个端点组成的正方形的权值和,判断是否不包括 1(即是否等于正方形元素个数和)即可。

    代码:

     1 #include <bits/stdc++.h>
     2 using namespace std;
     3 
     4 #define MAXN 105
     5 
     6 int n, m, a[MAXN][MAXN], sum[MAXN][MAXN], mx, ans;
     7 
     8 int main() {
     9     cin >> n >> m;
    10     for (int i = 1; i <= n; i++)
    11         for (int j = 1; j <= m; j++) {
    12             cin >> a[i][j];
    13             sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
    14         }
    15     for (int i = 1; i <= n; i++)
    16         for (int j = 1; j <= m; j++)
    17             for (int k = 1; k < i; k++)
    18                 for (int l = 1; l < j; l++)
    19                     if (i - k == j - l) {
    20                         int o = sum[i][j] - sum[i][l] - sum[k][j] + sum[k][l];
    21                         if (o == (i - k) * (j - l) && o > mx)
    22                             mx = o, ans = i - k;
    23                     }
    24     cout << ans;
    25     return 0;
    26 } 

    ② 高维前缀和

    和二维前缀和一样,根据容斥原理可得计算方法,只不过会更加麻烦,暂且不提。

  • 相关阅读:
    【leetcode_easy_array】1450. Number of Students Doing Homework at a Given Time
    【leetcode_easy_array】1295. Find Numbers with Even Number of Digits
    【leetcode_easy_array】1266. Minimum Time Visiting All Points
    【leetcode_easy_array】1260. Shift 2D Grid
    【leetcode_easy_array】1275. Find Winner on a Tic Tac Toe Game
    【leetcode_easy_array】1450. Number of Students Doing Homework at a Given Time
    【leetcode_easy_array】1287. Element Appearing More Than 25% In Sorted Array
    【leetcode_easy_array】1299. Replace Elements with Greatest Element on Right Side
    【leetcode_easy_array】1512. Number of Good Pairs
    【leetcode_easy_array】1252. Cells with Odd Values in a Matrix
  • 原文地址:https://www.cnblogs.com/jinkun113/p/12879719.html
Copyright © 2011-2022 走看看