【algorithm】认真讲解前缀和与差分 (图文搭配)

news2024/11/26 18:28:19

🚀write in front🚀
📝个人主页:认真写博客的夏目浅石.
📣系列专栏:AcWing算法笔记

今天的月色好美
在这里插入图片描述

文章目录

  • 前言
  • 一、前缀和算法
    • 1.1 什么是前缀和?
    • 1.2 一维前缀和
  • 二、二维前缀和
  • 三、一维差分
  • 四、二维差分
  • 总结


前言

这里介绍以下前缀和算法以及差分算法,用来梳理自己所学到的算法知识。


一、前缀和算法

1.1 什么是前缀和?

从我的理解角度来讲:前缀和就是高中数学当中的数列的求和Sn,差分就是前缀和的逆运算,就是递推公式。

1.2 一维前缀和

先来看一道题目吧:
在这里插入图片描述
这是之前训练的时候的一道经典的前缀和问题,我们很容易想到暴力作法:遍历数组

代码如下:

#include<stdio.h>
const int N = 1e5 + 10;
int a[N];
int n,m;
int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
	while(m--)
	{
    	int l, r;
    	int sum = 0;
    	scanf("%d%d", &l, &r);
    	for(int i = l; i <= r; i++)
    	{ 
     	   sum += a[i];
  	  	}
    	printf("%d\n",sum);
	}
	return 0;
}

这样的时间复杂度为O(n * m),如果n和m的数据量稍微大一点就有可能超时,而我们如果使用前缀和的方法来做的话就能够将时间复杂度降到O(n + m),大大提高了运算效率。

前缀和做法:

#include<stdio.h>
int main()
{
	long long n,k,arr[100010],sum[100010];
	scanf("%lld %lld",&n,&k);
	sum[0]=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&arr[i]);
		int tmp=arr[i];
		sum[i]=tmp+sum[i-1];
	}
	for(int i=1;i<=k;i++)
	{
		int f,t;
		scanf("%d %d",&f,&t);
		printf("%lld\n",sum[t]-sum[f-1]);//重要步骤
	}
	return 0;
}

原理讲解:

sum[r] = a[1] + a[2] + a[3] + a[l-1] + a[l] + a[l + 1] .. a[r];
sum[l - 1] = a[1] + a[2] + a[3] + ... + a[l - 1];
sum[r] - sum[l - 1] = a[l] + a[l + 1] + ... + a[r];

这样,对于每个询问,只需要执行 sum[r] - sum[l - 1]。输出原序列中从第l个数到第r个数的和的时间复杂度变成了O(1)。

我们把它叫做一维前缀和。

二、二维前缀和

先来看一道题目吧:

在这里插入图片描述

因为这里提及到了二维这个词,所以我们先来定义一个二维数组s[][] , s[i][j] 表示二维数组中,左上角(1, 1)到右下角(i, j)所包围的矩阵元素的和。接下来推导二维前缀和的公式。

先看一张图:
在这里插入图片描述
图解:

  • (1,1)(i,j-1)表示的面积是S1+S2定义为S黄蓝
  • (1,1)(i-1,j)表示的面积是S1+S3定义为S黄粉
  • (1,1)(i,j)表示的面积是S黄蓝+S黄粉-S1+S4

因此得出二维前缀和预处理公式

s[i][j] = s[i - 1][j] + s[i][j - 1 ] + a[i] [j] - s[i - 1][j - 1]

讲解完这些基础知识就可以去解决刚才的问题啦

#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int s[N][N];
int main()
{
    scanf("%d%d%d", &n, &m, &q);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            scanf("%d", &s[i][j]);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
    while (q -- )
    {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
    }
    return 0;
}

所以总结模板就是:

S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

三、一维差分

先看一道问题:
在这里插入图片描述

类似于数学中的求导和积分,差分可以看成前缀和的逆运算。
首先给定一个原数组aa[1], a[2] , , , , a[n];

然后我们构造一个数组b b[1], b[2] , , , b[i];

使得 a[i] = b[1] + b[2] + , , , + b[i]

也就是说,a数组是b数组的前缀和数组,反过来我们把b数组叫做a数组的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。

其实换个好理解的方式:
a[0 ]= 0;
b[1] = a[1] - a[0];
b[2] = a[2] - a[1];

b[n] = a[n] - a[n - 1];

但是知道了这些怎么用到题目上呢?或者换句话说,怎么就成为一种算法了呢?hhh下面就来解决这个问题哦~

如果给定区间[l, r ],让我们把a数组中的[l, r] 区间中的每一个数都加上c,即 a[l] + c , a[l + 1] + c , a[l + 2] + c ,,,,,, a[r] + c;

暴力做法是for循环l到r区间,时间复杂度O(n),如果我们需要对原数组执行m次这样的操作,时间复杂度就会变成O(n * m)。有没有更高效的做法吗? 考虑差分的做法。

首先让差分b数组中的 b[l] + c ,通过前缀和运算,a数组变成 a[l] + c ,a[l + 1] + c,,,,,, a[n] + c;

然后我们打个补丁,b[r + 1] - c, 通过前缀和运算,a数组变成 a[r + 1] - c,a[r + 2] - c,,,,,,,a[n] - c;

在这里插入图片描述
b[l] + c,效果使得a数组中 a[l] 及以后的数都加上了c(红色部分),但我们只要求l到r 区间加上 c, 因此还需要执行 b[r + 1] - c,让a数组中 a[r + 1]及往后的区间再减去c(绿色部分),这样对于a[r] 以后区间的数相当于没有发生改变。

因此我们得出一维差分结论:给a数组中的[ l, r] 区间中的每一个数都加上c,只需对差分数组bb[l] + = c,b[r+1] - = c 。时间复杂度为O(1), 大大提高了效率。

代码如下:

#include<stdio.h>
int main()
{
    int arr[100010],a[100010],n,m,q;
    scanf("%d%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&arr[i]);
    a[0]=0;
    for(int i=1;i<=n;i++)
    {
        a[i]=arr[i]-arr[i-1];
    }
    while(m--)
    {
        int l,r,c;
        scanf("%d%d%d",&l,&r,&c);
        a[l]+=c;
        a[r+1]-=c;
        
    }
    for(int i=1;i<=n;i++)
    {
        arr[i]=arr[i-1]+a[i];
        printf("%d ",arr[i]);
    }
    
    return 0;
}

四、二维差分

首先先看一道题目:
在这里插入图片描述
如果扩展到二维,我们需要让二维数组被选中的子矩阵中的每个元素的值加上c,是否也可以达到O(1)的时间复杂度。答案是可以的,考虑二维差分。

那么下面就来讲解二维差分

在这里插入图片描述

  • b[x1][y1] += c ; 对应图1 ,让整个a数组中黄色矩形面积的元素都加上了c。
  • b[x1,][y2 + 1] -= c ; 对应图2 ,让整个a数组中粉色+绿色矩形面积的元素再减去c,使其内元素不发生改变。
  • b[x2 + 1][y1] -= c ; 对应图3 ,让整个a数组中蓝色+绿色矩形面积的元素再减去c,使其内元素不发生改变。
  • b[x2 + 1][y2 + 1] += c; 对应图4,让整个a数组中绿色矩形面积的元素再加上c,绿色内的相当于被减了两次,再加上一次c,才能使其恢复。

模板:

void insert(int x1,int y1,int x2,int y2,int c)
{
    b[x1][y1]+=c;
    b[x1][y2+1]-=c;
    b[x2+1][y1]-=c;
    b[x2+1][y2+1]+=c;
}

在这里插入图片描述

b[i][j] = a[i][j] − a[i − 1][j] − a[i][j − 1] + a[i −1 ][j − 1]
代码如下:

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

const int N = 1e3 + 10;
int a[N][N],b[N][N];
int n,m,q;

void insert(int x1,int y1,int x2,int y2,int c)
{
    b[x1][y1]+=c;
    b[x1][y2+1]-=c;
    b[x2+1][y1]-=c;
    b[x2+1][y2+1]+=c;
}

int main()
{
    cin>>n>>m>>q;
    
    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++)
        {
            insert(i,j,i,j,a[i][j]);//构造差分数组
        }
    }
    
    while(q--)
    {
        int x1,y1,x2,y2,c;
        cin>>x1>>y1>>x2>>y2>>c;
        insert(x1,y1,x2,y2,c);//进行差分
    }
    
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
        }
    }
    
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            printf("%d ",b[i][j]);
        }
        printf("\n");
    }

}

总结

今天学习了前缀和算法知识,每天进步一点点,不积硅步,无以至千里
我们下期见吧~

在这里插入图片描述
如果无聊的话,就来逛逛我的博客栈吧stack-frame.cn

原创不易,还希望各位大佬支持一下 \textcolor{blue}{原创不易,还希望各位大佬支持一下} 原创不易,还希望各位大佬支持一下

👍 点赞,你的认可是我创作的动力! \textcolor{9c81c1}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!

⭐️ 收藏,你的青睐是我努力的方向! \textcolor{ed7976}{收藏,你的青睐是我努力的方向!} 收藏,你的青睐是我努力的方向!

✏️ 评论,你的意见是我进步的财富! \textcolor{98c091}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!

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

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

相关文章

Java---微服务---分布式搜索引擎elasticsearch(1)

分布式搜索引擎elasticsearch&#xff08;1&#xff09;1.elasticsearch1.1.了解ES1.1.1.elasticsearch的作用1.1.2.ELK技术栈1.1.3.elasticsearch和lucene1.1.4.为什么不是其他搜索技术&#xff1f;1.1.5.总结1.2.倒排索引1.2.1.正向索引1.2.2.倒排索引1.2.3.正向和倒排1.3.es…

表单标签的使用

1、input标签 场景&#xff1a;在网页中显示收集用户消息的表单效果&#xff0c;如登录页、注册页 通过type属性值的不同&#xff0c;展示不同的效果 type属性值说明text文本框&#xff0c;用于输入单行文本password密码框&#xff0c;用于输入密码radio单选框&#xff0c;用…

检查 malloc 函数返回内容的四个理由

写在前面&#xff1a; 一些开发人员可能对检查不屑一顾&#xff1a;他们故意不检查malloc函数是否分配了内存。他们的推理很简单——他们认为会有足够的记忆。如果没有足够的内存来完成操作&#xff0c;请让程序崩溃。似乎是一个糟糕的事实。 注意。在本文中&#xff0c;mall…

Opencv 之 DNN 与 CUDA综述

Opencv 之 DNN 与 CUDA 目录 Opencv官方手稿&#xff08;包含各模块API介绍及使用例程&#xff09; Opencv在github的仓库地址&#xff1a;https://github.com/opencvOpencv额外的测试数据 下载&#xff1a;https://github.com/opencv/opencv_extra #可通过git下载拉取 git c…

【算法练习】删除链表的节点

题源&#xff1a;牛客描述给定单向链表的头指针和一个要删除的节点的值&#xff0c;定义一个函数删除该节点。返回删除后的链表的头节点。1.此题对比原题有改动2.题目保证链表中节点的值互不相同3.该题只会输出返回的链表和结果做对比&#xff0c;所以若使用 C 或 C 语言&#…

pix2pix(二)训练图像尺寸及分配显卡

背景&#xff1a;新的数据集上&#xff0c;图像的大小为496496&#xff0c;与原尺寸512512不一样&#xff0c;不知道能否直接运行。另外&#xff0c;我们现在有了四张空余显卡服务器&#xff0c;并且新数据集的数据量较大&#xff0c;我们有空余的显卡资源加快训练。 目的&…

C++ 模板

在学习stl之前&#xff0c;我们就已经略微讲解了一些模板的知识&#xff0c;而现在&#xff0c;我们来进一步了解一下模板的相关知识 初步了解 目录 一. 非类型模板参数 二. 模板的特化 全特化 偏特化 三. 模板分离编译 四. 总结 一. 非类型模板参数 模板参数…

C进阶_结构体内存对齐

请看下面的代码&#xff0c;输出结果是多少&#xff1f; #include <stdio.h> int main() {struct S1{char c1;int i;char c2;};printf("%d\n", sizeof(struct S1));struct S2{char c1;char c2;int i;};printf("%d\n", sizeof(struct S2));return 0;…

Xmake v2.7.6 发布,新增 Verilog 和 C++ Modules 分发支持

Xmake 是一个基于 Lua 的轻量级跨平台构建工具。 它非常的轻量&#xff0c;没有任何依赖&#xff0c;因为它内置了 Lua 运行时。 它使用 xmake.lua 维护项目构建&#xff0c;相比 makefile/CMakeLists.txt&#xff0c;配置语法更加简洁直观&#xff0c;对新手非常友好&#x…

前端CSS学习之路-css002

&#x1f60a;博主页面&#xff1a;鱿年年 &#x1f449;博主推荐专栏&#xff1a;《WEB前端》&#x1f448; ​&#x1f493;博主格言&#xff1a;追风赶月莫停留&#xff0c;平芜尽处是春山❤️ 目录 CSS字体属性 一、字体系列 二、字体大小 三、字体粗细 四、文字样…

Docker安装nacos

首先将自己的服务器在配置上弄成docker的 然后再下方命令框中直接粘贴如下命令&#xff1a; docker run –name nacos -d -p 8848:8848 -p 9848:9848 -p 9849:9849 –restartalways -e JVM_XMS256m -e JVM_XMX256m -e MODEstandalone -v /usr/local/nacos/logs:/home…

基于多协议传感器的桥梁监测数据采集与管理系统设计

文章目录前言1、要求&#xff1a;2、系统框图2.1系统总体框图2.2、stm32通过AHT20采集温湿度框图&#xff1a;2.3、stm32通过modbus协议与上位机通信框图&#xff1a;3、ModBus协议1、协议概述2、Modbus主/从协议原理3、通用Modbus帧结构---协议数据单元(PDU)4、两种Modbus串行…

readonly与disabled对比

<!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>readonly与disabled</title> </head> <body> <!--readonly与disabled 都是只读不能修改…

传统推荐模型(二)协同过滤的进化——矩阵分解算法

传统推荐模型&#xff08;二&#xff09;协同过滤的进化——矩阵分解算法 针对协同过滤算法的头部效应较明显、泛化能力较弱的问题&#xff0c;矩阵分解算法被提出。矩阵分解在协同过滤算法中“共现矩阵”的基础上&#xff0c;加人了隐向量的概念&#xff0c;加强了模型处理稀…

动态顺序表——简单的增删查改

前言 &#xff1a;从这篇博客开始&#xff0c;我会进行数据结构(用C语言实现)有关内容的记录与分享。对于我们而言&#xff0c;数据结构的知识难度较大并且十分重要&#xff0c;希望我的分享给各位带来一些帮助。而今天要分享的就是数据结构中最简单的知识——顺序表的增删查改…

11.Java方法的综合练习题大全-双色球彩票系统,数字的加密和解密等试题

本篇文章是Java方法的专题练习,从第五题开始难度增大,涉及大厂真题,前四道题目是基础练习,友友们可有目的性的选择学习&#x1f618;&#x1f495; 文章目录前言一、数组的遍历1.注意点:输出语句的用法2.题目正解二、数组最大值三、判断是否存在四、复制数组五、案例一:卖飞机票…

【学习笔记之数据结构】二叉树(一)

二叉树的概念&#xff1a; 二叉树是一种树的度不大于2的树&#xff0c;也就是它的节点的度都是小于等于2的。二叉树的子树有左右之分&#xff0c;左右的次序不能颠倒&#xff0c;因此二叉树是一个有序树。任意的二叉树都由空树、只有根节点、只有左子树、只有右子树、左右子树均…

一个简单的自托管图片库HomeGallery

什么是 HomeGallery &#xff1f; HomeGallery 是一个自托管的开源 Web 图片库&#xff0c;用于浏览个人照片和视频&#xff0c;其中包括标记、对移动端友好和 AI 驱动的图像和面部发现功能。 HomeGallery 的独特功能是自动 相似图像/反向图像搜索功能 和 无数据库架构 &#x…

实验三、8人智力竞赛抢答电路设计

实验三 8人智力竞赛抢答电路设计 实验目的 设计一个能支持八路抢答的智力竞赛抢答器&#xff1b;主持人按下开始抢答的按键后&#xff0c;有短暂的报警声提示抢答人员抢答开始且指示灯亮表示抢答进行中&#xff1b;在开始抢答后数码管显8秒倒计时&#xff1b;有抢答人员按下抢…

Linux企业应用现状

一、Linux在服务器领域的发展 随着开源软件在世界范围内影响力日益增强&#xff0c;Linux服务器操作系统在整个服务器操作系统市场格局中占据了越来越多的市场份额&#xff0c;已经形成了大规模市场应用的局面。并且保持着快速的增长率。尤其在政府、金融、农业、交通、电信等国…