差分与前缀和的含义、应用及示例代码

news2024/11/26 4:44:51

差分与前缀和

求差分 与 求前缀和 是一组“互逆”的操作。

使用差分 可以实现:以时间复杂度为O(1),对数组区间各元素 / 矩阵区域各元素 ± 一个常数

使用前缀和 可以实现:以时间复杂度为O(1),对数组区间各元素 / 矩阵区域各元素 进行快速地 求和

为了便于理解,可以将差分与前缀和理解为 数列各项数列的前n项和

  • 前缀和:Sn = A1 + A2 + A3 + … + An;

  • 差分(为了避免歧义,此处将下标用括号括出):A(n) = S(n) - S(n-1);

以下为差分与前缀和的具体内容:

1. 前缀和

前缀和是一种常用的算法技巧,通常用于快速求解 数组区间求和矩阵区域求和 的问题。

1.1 前缀和数组

已知数组 a[n] ,遍历数组并计算 每个位置之前所有元素的和,将其存储到数组s[n]中,以便在之后的查询中可以快速得到区间和。【注:为了便于处理,前缀和的数组下标从 1 开始,且s[0] = a[0] = 0】

  • 前缀和的定义

    • s[1] = a[1]
    • s[2] = a[1] + a[2]
    • s[3] = a[1] + a[2] +a[3]
    • s[n] = a[1] + a[2] + a[3] + … + a[n]

    根据递推关系,可以得出: s[i] = s[i - 1] + a[i]

  • 若要求 a[l] + a[l + 1] + … + a[r] ,即数组下标范围为[l, r]的元素之和 (暂写作a[l, r])。则有:

    a[l, r] = s[r] - s[l - 1]

    在这里插入图片描述

  • 代码模板

    //初始化
    a[0] = 0;
    s[0] = 0;
    
    //数据输入
    //前缀和,数组的下标从1开始
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
    }
    
    //计算前缀和
    for(int i = 1; i <= n; i++){
        s[i] = s[i - 1] + a[i];
    }
    
    //计算a[l] + a[l + 1] + ... + a[r]
    //(此处存在多个求和操作,仅写一个作为示例)
    ans = s[r] - s[l - 1];
    

1.2 前缀和矩阵

已知矩阵 a[n][m],在遍历矩阵的过程中,将矩阵各个点与原点围成的矩形之中的所有元素的和预先计算出来,并存储到s[n][m]中,在之后的查询中可以快速得到区域和。

  • 前缀和的定义

    s[i][j] = a[1][1] + a[1][2] + ... + a[1][j]
             +a[2][1] + a[2][2] + ... + a[2][j]
             + ...
             +a[i][1] + a[i][2] + ... + a[i][j]
    

    在这里插入图片描述

  • 根据容斥原理,可以发现,橙色区域,如果扣除蓝色和青灰色区域的元素和的话,会把红色区域扣除两次,所以,橙色区域元素和在减去蓝色和青灰色区域的元素和之后,还需要加上红色区域,即所求区域【[x1,y1]和[x2,y2]确定的区域】的元素和为:

    s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 -1][y1 - 1]

  • 代码模板

    //初始化:
    //需要把 s[0][0] ... s[0][m] 与 s[0][0] ... s[n][0] 初始化为0
    
    //输入
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= m; j ++){
            scanf("%d",&a[i][j]);
        }
    }
    
    //计算前缀和
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= m; j ++){
            // s[i - 1][j] 和 s[i][j - 1] 均覆盖 s[i - 1][j - 1]区域
            // 即 s[i - 1][j - 1] 区域被计算了2次,需要减去1个 s[i - 1][j - 1]
            s[i][j] = a[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
        }
    }
    
    //(此处存在多个求和操作,仅写一个作为示例)
    ans = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 -1][y1 - 1];
    
  • 备注:关于前缀和的计算

    在这里插入图片描述

2. 差分

差分可看作前缀和的逆运算,常用于处理连续序列的增量变化。它可以在一维数组或二维矩阵中快速且高效地更新区间/区域的元素值。

2.1 差分数组

已知数组a[n],当存在大量的数组区间操作(增/减常数值)时,可以先求出该数组的差分数组b[n],然后再进行变更操作,最后通过差分数组还原出变更后的数组a[n]。

  • 差分的定义

    b[1] = a[1]
    b[2] = a[2] - a[1]
    b[3] = a[3] - a[2]
    b[4] = a[4] - a[3]
    ...
    b[n] = a[n] - a[n - 1];
    
    如果对左右两边分别累加,可得:
    a[n] = b[1] + b[2] + b[3] + ... +b[n]
    
  • 当需要给 a[l], a[l + 1], a[l + 2] ... a[r] 分别加上 常数c 的时候,仅需进行以下操作即可实现:

    • b[l] = b[l] + c

    • b[r + 1] = b[r + 1] - c

    • 如下图所示:

      • a[l]之前和a[r]之后(不包括a[l]和a[r])的元素均不受影响
      • a[l]之前和a[r]之后(包括a[l]和a[r])的元素均增加了c
      • 注意:此处仅对b[n]数组进行操作,需要累加求和才能得出变更后的数组a[n]。

      在这里插入图片描述

  • 代码模板

    //初始化时,将a[n]与b[n]中的所有元素均为0
    //此时,满足 b[n]是a[n]数组的差分数组 的条件(b[n]所有值均为0,累加后也均为0)
    
    //输入
    //下标从1开始
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    //差分数组初始化
    //因为当前a[n]与b[n]中的所有元素均为0,满足b[n]是a[n]的差分数组
    //此时可以看作,在数组a[n]中,依次把区间[i,i]的数均加上常数a[i] (注:此处i的取值范围为[1, n])
    //因此,在插入a[i]时,只需令
    //    b[i] = b[i] + a[i]; 
    //    b[i + 1] = b[i + 1] - a[i];
    for(int i = 1; i <= n; i++){
        b[i] += a[i];
        b[i+1] -= a[i];
    }
    
    //进行区间操作(此处存在多个区间操作,仅写一个作为示例)
    //给 a[l], a[l + 1], a[l + 2] ... a[r] 加 c
    scanf("%d%d%d", &l, &r, &c);
    b[l] += c;
    b[r + 1] -= c;
    
    
    //最后计算数组a[n]并输出
    for(int i = 1; i <= n; i++){
        a[i] = b[i] + a[i - 1];
        printf("%d ", a[i]);
    }
    

2.2 差分矩阵

类比于一维数组,在面对“给矩阵 a[n][m] 的子矩阵中每个元素加/减一个常数”的情况时,也可以构造矩阵 a[n][m] 的差分矩阵 b[n][m] (此时 a[n][m]b[n][m] 的前缀和矩阵),来实现在O(1)时间复杂度的情况下进行子矩阵各元素的变更。

  • 差分的定义

    • 对于a[n][m]的差分数组b[n][m],(矩阵的下标均从1开始)有:
      
      a[i][j] = b[1][1] + b[1][2] + ... + b[1][j]
               +b[2][1] + b[2][2] + ... + b[2][j]
               + ...
               +b[i][1] + b[i][2] + ... + b[i][j]
      
    • 当需要给(x1,y1)(x2,y2)确定的子矩阵(目标区域,如下图所示)中的每个元素 +c 时,需要进行以下操作:

      • b[x1][y1] = b[x1][y1] + c
      • b[x1][y2 + 1] = b[x1][y2 + 1] - c
      • b[x2 + 1][y1] = b[x2 + 1][y1] - c
      • b[x2 + 1][y2 + 1] = b[x2 + 1][y2 + 1] + c
    • 在这里插入图片描述

  • 代码模板

    //初始化时,将a[n][m]与b[n][m]中的所有元素均赋值为0
    //此时,满足:
    //  a[n][m] 是 b[n][m] 的前缀和矩阵
    //  b[n][m] 是 a[n][m] 的差分矩阵
    
    //矩阵的输入
    //下标均从1开始
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            scanf("%d", &a[i][j]);
        }
    }
    
    //差分矩阵初始化
    //因为 b[n][m] 是 a[n][m] 的差分矩阵(所有的值全部为0)
    //此时可以看作对点(i, j) 进行 +c 的操作(令x1 = x2, y1 = y2, 矩形区域就变成了一个点)
    //我们已知 (x1,y1)与(x2,y2)确定的子矩阵中的每个元素都 +c 的操作,所以可以:
    //令 x1 = x2 = i; y1 = y2 = j;  c = a[i][j]
    //按照这种方式,依次遍历所有元素就可以完成对差分矩阵的初始化,代码如下:
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            b[i][j] += a[i][j];
            b[i][j + 1] -= a[i][j];
            b[i + 1][j] -= a[i][j];
            b[i + 1][j + 1] += a[i][j];
        }
    }
    
    
    //进行区域操作(此处存在多个区域操作,仅写一个作为示例)
    //给(x1,y1)与(x2,y2)确定的子矩阵(目标区域,如下图所示)中的每个元素 +c:
    scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
    b[x1][y1] += c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1][y2 + 1] += c;
    
    
    //最后计算矩阵a[n][m]并输出。
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            a[i][j] = b[i][j] + a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
    
    
  • 备注:关于差分矩阵的初始化

  • 在这里插入图片描述

本文是学习AcWing算法基础课的总结,请见:
https://www.acwing.com/activity/content/11/

如有不当或错误之处,恳请您的指正,谢谢!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1451323.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 图上 bfs(五十八)【第五篇】

今天我们来学习一下图上bfs。 1.图上bfs 在图上&#xff0c;我们也可以进行 BFS&#xff0c;也可以解决图上 DFS 能解决的问题&#xff0c;比如连通块。 除此以外&#xff0c;根据 BFS 的性质&#xff0c;第一次到一个点的时候记下来的步数一定是到从起点到这个点的最小步数&…

Netty Review - 直接内存的应用及源码分析

文章目录 Pre概述应用访问效率&#xff1a; 堆内存 VS 直接内存申请效率&#xff1a; 堆内存 VS 直接内存数据存储结构&#xff1a; 堆内存 VS 直接内存结论 ByteBuffer.allocateDirect 源码分析unsafe.allocateMemory(size) ---> C方法 JVM参数 -XX:MaxDirectMemorySize直接…

并发编程(1)基础篇

1 概览 1.1 这门课讲什么 这门课中的【并发】一词涵盖了在 Java 平台上的 进程线程并发并行 以及 Java 并发工具、并发问题以及解决方案&#xff0c;同时也会讲解一些其它领域的并发 1.2 为什么学这么课 我工作中用不到并发啊&#xff1f; 那你还是没有接触到复杂项目. …

【Python--网络编程之Ping命令的实现】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Python开发技术 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; Python网络编程之Ping命令的实现 往期内容代码见资源&#xff0c;效果图如下一、实验要求二、协…

【AIGC】Stable Diffusion 的提示词入门

一、正向提示词和反向提示词 Stable Diffusion 中的提示词通常用于指导用户对生成的图像进行控制。这些提示词可以分为正向提示词&#xff08;Positive Prompts&#xff09;和反向提示词&#xff08;Negative Prompts&#xff09;两类&#xff0c;它们分别影响图像生成过程中的…

嵌入式Qt Qt中的字符串类

一.Qt中的字符串类 QString vs string&#xff1a; QString在Qt库中几乎是无所不在的 所有的Qt图形用户组件都依赖于QString 实验1 &#xff1a;QString 初体验 #include <QDebug> void Sample_1() {QString s "add";s.append(" "); // &q…

函数递归与迭代附n的阶乘+顺序打印一个整数的每一位数+求第n个斐波那契数

1. 什么是递归&#xff1f; 递归其实是一种解决问题的方法&#xff0c;在C语言中&#xff0c;递归就是函数自己调用自己。 下面是一个最简单的C语言递归代码&#xff1a; #include <stdio.h> int main() {printf("hehe\n");main();//main函数中⼜调⽤了main函数…

linux进程控制【程序替换】

目录 前言&#xff1a; 1.替换原理 ​编辑 2.替换函数 2.1函数 execl 2.2函数 execv 2.3函数 execlp 2.4函数 execvp 2.5函数 execle 2.6函数 execve 2.7函数 execvpe 前言&#xff1a; 前面我们介绍了进程控制中的创建&#xff0c;退出等待&#xff0c;本章节我们将…

【VSCode】使用笔记

目录 快捷键系列 相关插件 相关文档链接 快捷键系列 调出终端 ctrl 或者是ctrlJ 结束进程 ctrlc 注释 ctrlkc 取消注释 ctrlku 上下移动代码 alt方向键 多行光标ctrlalt方向键 快速跳过某个单词 ctrl方向键 相关插件 1.每次修改后&#xff0c;自动保存启动项目 相…

家人们,比赛打完了

啊&#xff0c;终于打完一场比赛了&#xff0c;但还有三场…… 先看看我的战绩&#xff1a; 共八题&#xff0c;AC6题&#xff0c;总共3902分&#xff0c;3.7k人参加&#xff0c;第980名 来看看第一&#xff1a; A8题&#xff0c;我只有2题没做出&#xff0c;相差4000多分&am…

NLP_ChatGPT的RLHF实战

文章目录 介绍小结 介绍 ChatGPT 之所以成为ChatGPT&#xff0c;基于人类反馈的强化学习是其中重要的一环。而ChatGPT 的训练工程称得上是复杂而又神秘的&#xff0c;迄今为止&#xff0c;OpenAl也没有开源它的训练及调优的细节。 从 OpenAl已经公开的一部分信息推知&#xff…

H5 粒子特效引导页源码

H5 粒子特效引导页源码 源码介绍&#xff1a;一款粒子特效引导页源码&#xff0c;带彩色文字和4个按钮。 下载地址&#xff1a; https://www.changyouzuhao.cn/10222.html

比较不相交5点结构的顺序

( A, B )---6*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有6个节点&#xff0c;AB训练集各由6张二值化的图片组成&#xff0c;让差值结构有5个点&#xff0c;收敛误差7e-4&#xff0c;收敛199次&#xff0c;统计迭代次数平均值并排序。 如果行和列可以自由的变换&#xff0c;5个…

A. Desorting

链接 : Problem - A - Codeforces 题意 : 思路 : 先判断序列是否排好序 &#xff0c; 不是排好序的&#xff0c;直接输出0即可&#xff0c;排好序的 : 先求出相邻元素之间的最小间隔&#xff0c;因为&#xff0c;要使有序非递减序列&#xff0c;变得不排序&#xff0c;…

Python三级考试笔记

Python三级考试笔记【源源老师】 三级标准 一、 理解编码、数制的基本概念&#xff0c;并且会应用。 1. 能够进行二进制、十进制以及十六进制之间的转换&#xff1b; 2. 理解Python中的数制转换函数。 二、 掌握一维数据的表示和读写方法&#xff0c;能够编写程序处理一维数据…

高效货运 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 老李是货运公司承运人&#xff0c;老李的货车额定载货重量为wt&#xff1b;现有两种货物&#xff0c;货物A单件重量为wa&#xff0c;单件运费利润为pa&#xff0c…

函数、极限、连续——刷题(3

目录 1.题目&#xff1a;2.解题思路和步骤&#xff1a;3.总结&#xff1a;小结&#xff1a; 1.题目&#xff1a; 2.解题思路和步骤&#xff1a; 3.总结&#xff1a; 首先还是考虑好所有情况&#xff08;所有情况见&#xff1a;函数、极限、连续——刷题&#xff08;1&#xff…

适用于Android 的 7 大短信恢复应用程序

对于 Android 用户来说&#xff0c;丢失重要的短信可能是一种令人沮丧的体验。幸运的是&#xff0c;有许多短信恢复应用程序可以帮助恢复丢失或删除的短信。在本文中&#xff0c;将与您分享 7 个最佳短信恢复应用程序&#xff0c;并帮助您找到可用于恢复已删除消息的最佳应用程…

机器学习分类评估四个术语TP,FP,FN,TN

分类评估方法主要功能是用来评估分类算法的好坏&#xff0c;而评估一个分类器算法的好坏又包括许多项指标。了解各种评估方法&#xff0c;在实际应用中选择正确的评估方法是十分重要的。 这里首先介绍几个常见的模型评价术语&#xff0c;现在假设我们的分类目标只有两类&#x…

LeetCode.107. 二叉树的层序遍历 II

题目 107. 二叉树的层序遍历 II 分析 这个题目考查的是二叉树的层序遍历&#xff0c;对于二叉树的层序遍历&#xff0c;我们需要借助 队列 这种数据结构。再来回归本题 &#xff0c;我们只需要将 二叉树的层序遍历的结果逆序&#xff0c;就可以得到这道题我们要求的答案了。…