首页 > 其他分享 >差分与前缀和学习笔记

差分与前缀和学习笔记

时间:2023-11-22 20:45:23浏览次数:32  
标签:前缀 val sum 元素 笔记 差分 数列

本来是不想写这篇博客的,但为了课前十分钟还是来水一发

前缀和

简介

继续引用OI-Wiki的话(OI-Wiki $yyds$ !):

前缀和可以简单理解为「数列的前 $n$ 项的和」,是一种重要的预处理方式,能大大降低查询的时间复杂度。

也就是说,我们能使用 $O(n)$ 的时间进行预处理,在 $O(1)$ 的时间内求出数列的一段区间内所有数的和。

为什么要使用前缀和?

假如我们有一段长为 $1e6$ 的数列,有 $1e6$ 个询问,假如我们使用传统的方式,对每一个询问以一个一个相加的方式进行求和,那么最坏情况下这个程序要运行 $1e12$ 次,妥妥的 $TLE$ 。但是我们使用前缀和的话就可以免去这些操作了。

那么怎么做?

假设给出的数列为 $a$ ,我们可以构造出一个相应的数列 $sum$ ,其中第 $x$ 个元素的值为 $a$ 数列中 $1$ ~ $x$ 个元素的和,即:$ sum_x = \sum\limits_{i = 1} ^ x a_i$ 。
那么显然,如果我们要求出从第 $l$ 个元素到第 $r$ 个元素,只要求出 $sum_r - sum_{l - 1}$ 的值就可以了。
而 $sum$ 数列中每一个元素的值,我们可以用前一个元素的值加上 $a$ 数列中对应位置元素的值求的。特别地,$sum$ 数列中第一个元素的值就等于 $a$ 数列中第一个元素的值。
示范代码如下:

cin >> n >> q;
for (int i = 1; i <= n; i++){
    cin >> a[i];
    sum[i] = a[i] + sum[i - 1];
}
while(q--){
    cin >> l >> r;
    cout << sum[r] - sum[l - 1] << endl;
}

扩展:二维前缀和

前面我们的讨论都是在一个一位的数列上进行的,但如果题目给我们的是一个二维的矩阵,阁下又当如何应对?
显然的,我们同样可以构造一个矩阵 $sum$ 使得 $sum_{x, y} = \sum\limits_{i = 1} ^ x \sum\limits_{j = 1} ^ y a_{i, j}$ 。怎么求出 $sum_{x, y}$ 的值呢?
首先,我们要加上 $a_{x, y}$ 的值。然后根据容斥原理,我们会重复加上一部分的前缀和,于是需要减去一部分。
如下图所示。我们要计算整块前缀和,就要先加上右下角这些,再加上左侧的所有前缀和和上面的前缀和。这时你会发现,我们把左上角的那一部分算了两次,于是我们减掉一次就好了。

那么怎么查询特定块的前缀和呢?
再看下面这张图。我们现在要计算红色部分的前缀和,而红色部分的前缀和等于总的前缀和 $-$ 它正上方的前缀和(蓝色部分) $-$ 它左侧的前缀和(绿色部分) $-$ 它左上方的前缀和(黄色部分)。根据二位前缀和的定义,我们可以求得它左侧和左上方的总前缀和(绿色和黄色部分),它正上方和左上方的总前缀和(蓝色和黄色部分)。再次根据容斥原理,我们两次减去了黄色部分,应该加回一次。这就是红色部分的前缀和。

下面是示范代码:

// 处理前缀和
cin >> n >> m >> q;
for(int i = 1; i <= n; i++)
    for(int j = 1; j <= m; j++)
        cin >> a[i][j];
for(int i = 1; i <= n; i++)
    for(int j = 1; j <= m; j++)
        sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
// 查询前缀和
while(q --){
    int l1, r1, l2, r2;
    cin >> l1 >> r1 >> l2 >> r2;
    cout << sum[l2][r2] - sum[l1 - 1][r2] - sum[l2][r1 - 1] + sum[l1 - 1][r1 - 1] << endl;
}

差分

简介

再次引用OI-Wiki的话:

差分是一种和前缀和相对的策略,可以当做是求和的逆运算。

用人话来说,差分支持我们通过前缀和的方式,实现 $O(1)$ 对一个数列中一段区间元素的值进行修改,并且方便的得到每一个值。

为什么要使用差分

同理,使用差分可以避免对数列中每个元素的值一个一个进行修改,使得程序的运行时间大大减少。

那么怎么做?

针对给出的数列 $a$ ,我们可以构造出一个数列 $b$ ,使得其中每一个元素的值都是 $a$ 数列中对应元素的值减去前一个元素的值,也就是 $b_i = a_i - a_{i - 1}$
那么我们可以得出这样的一个式子:$a_x = \sum\limits_{i = 1} ^ x b_i$。很显然,我们可以利用前缀和求出 $a$ 中元素的值。
但这样显然没有任何优势,但是差分的优势在于对区间元素的修改。假如我们要给 $l$ 到 $r$ 之间的元素都加上一个值 $val$ ,我们只需要给 $b_l$ 的值加上 $val$ ,给 $b_{r + 1}$ 的值减去 $val$ 就可以了。
解释以下。根据差分的定义,给 $b_l$ 的值加上 $val$ 就相当于给 $a_l$ 到数列末尾元素的值都加上 $val$ ,那么给 $b_{r + 1}$ 减去 $val$ 就可以抵消其对于 $a_{r + 1}$ 到数列末尾的影响了。
示范代码如下:

cin >> n >> q;
for (int i = 1; i <= n; i++){
    cin >> a[i];
    b[i] = a[i] - a[i - 1];
}
while(q--){
    cin >> l >> r >> val;
    b[l] += val;
    b[r + 1] -= val;
}
for (int i = 1; i <= n; i++){
    sum[i] = sum[i - 1] + b[i];
    cout << sum[i] << ' ';
}

标签:前缀,val,sum,元素,笔记,差分,数列
From: https://www.cnblogs.com/Floze3/p/17850247.html

相关文章

  • openGauss学习笔记-130 openGauss 数据库管理-参数设置-重设参数
    openGauss学习笔记-130openGauss数据库管理-参数设置-重设参数130.1背景信息openGauss提供了多种修改GUC参数的方法,用户可以方便的针对数据库、用户、会话进行设置。参数名称不区分大小写。参数取值有整型、浮点型、字符串、布尔型和枚举型五类。布尔值可以是(on,off)、(true......
  • 图论杂项 学习笔记
    图论专题新学的知识点有点太多了,还是新开一篇比较好。数据结构优化建图reference:常见优化建图技巧。考虑一个题:CF786B。思考如何维护单点/区间向单点/区间连边:点对点。直接连就行。点对区间。开一颗线段树,每个节点代表一段区间,父亲向儿子连权为\(0\)的边。点对区间连边......
  • 算法学习笔记(41): 朴素多项式算法
    朴素多项式算法-\(O(n^2)\)合集我们并不需要NTT,就算需要,也只是用来优化乘法。多项式求逆对于多项式\(\suma_ix^i\)我们需要构造出一个多项式\(\sumb_ix^i\)使得:\[\begin{cases}a_0b_0=1\\\sum_{i=0}^ka_ib_{k-i}=0&k\ge1\end{cases}\]首先\(......
  • acwing 第 130 场周赛  (前缀和,dfs,对不同边的处理)
      #include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<climits>usingnamespacestd;typedeflonglongLL;constintN=5010;intn;inta[N];LLs[N];LLget(intl,intr){return......
  • 【笔记】C++系列02:连续的作用域解析运算符::的场景有哪些?
    在C++中,可以使用连续的作用域解析运算符::来访问嵌套的命名空间、类和类成员。这种用法通常在以下场景下出现:命名空间嵌套:当命名空间中存在嵌套的命名空间时,可以使用连续的作用域解析运算符来访问内层命名空间中的成员。例如:namespaceA{namespaceB{namespac......
  • 前缀和
    前缀和1.前缀和简介前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,(而差分可以看成前缀和的逆运算).合理的使用前缀和与差分,可以将某些复杂的问题简单化。2.前缀和好处求数组的某个区间的和时,最容易想出暴力算法,遍历区间求和,时间复杂度为O(n*m)而使用前缀......
  • 2023.11.22学习笔记(2)
    跳石头P2678[NOIP2015提高组]跳石头-洛谷|计算机科学教育新生态(luogu.com.cn)佬啊佬啊,我的思路:用数组b去储存它的差分,每一次找到它的最小值,将最小值和它旁边的较小的那个值合并,边界的话就直接合并,总计进行m次合并操作,这个时候再找到它的最小值,就是答案但是如果是枚举......
  • 代码整洁之道笔记3
    四.注释1.若编程语言足够有表达力,就不需要注释2.注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。注释总是一种失败3.程序员应当负责将注释保持在可维护、有关联、精确的高度,更应该把力气用在写清楚代码上,直接保证无须编写注释4.不准确的注释要比没注释坏得多注释不能......
  • 算法学习笔记(40): 具体数学
    具体数学本文是阅读《具体数学》产生的理解性文本,并且涵盖了部分其他相关的内容。不怎么重要或者太难的东西因为时间问题,我略过了。本文来之不易,请勿机械搬运:原文地址-https://www.cnblogs.com/jeefy/p/17848037.html目录具体数学第二章-和式和式的处理有限微积分分部求和......
  • 代码整洁之道笔记4
    七.错误信息错误处理很重要,但如果它搞乱了代码逻辑,就是错误的做法使用异常而非返回码1.遇到错误时,最好抛出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱先写Try-Catch-Finally语句1.异常的妙处之一是,它们在程序中定义了一个范围。执行try-catch-finally语句中try部分的......