前缀和与差分(二维)

news/2024/12/22 14:02:30/

二维前缀和

下面是一个二维数组,我们要求(1,1)到(2,2)区间内的所有元素的和,最原始的方法就是遍历每个元素然后一个一个加起来,此时时间复杂度为O(n*m)。
在这里插入图片描述
我们之前学过一维数组的前缀和那么我们可以这样优化一下,
在这里插入图片描述
此时时间复杂度优化到了O(min{n,m})。还可以在优化吗?
答案是可以的。
我们依然根据一维数组的前缀和的思想来考虑二维数组
在这里插入图片描述
在上面的公式中我们发现出现了i-1和j-1,那么我们就需要考虑边界防止i或j为0时造成数组越界,
1.当i=0j=0 我们可以得到sum[0][0]=g[0][0]sum{0,0}{0,0}=g[0][0]
2.当i=0 我们可以得到sum[0][i]=sum[0][i-1]+g[0][i](一维数组前缀和)
sum{0,y1}{0,y2}=sum[0][y2]-sum[0][y1-1]
3.当j=0 我们可以得到sum[i][0]=sum[i-1][0]+g[i][0](一维数组前缀和)和sum{x1,0}{x2,0}=sum[x2][0]-sum[x1-1][0]
在这里插入图片描述

代码如下

#include<iostream> // 包含输入输出流的头文件
using namespace std; // 使用标准命名空间const int n = 3, m = 4; // 定义二维数组的行数n和列数m
int g[n][m] = { {1,5,6,8},{9,6,7,3},{5,3,2,4}}; // 初始化二维数组g
int sum[n][m]; // 用于存储前缀和的二维数组// 计算二维数组的前缀和
void pre_sum()
{sum[0][0] = g[0][0]; // 左上角元素的前缀和为其自身// 计算第一列的前缀和,只需逐行累加for (int i = 1; i < n; i++){sum[i][0] = sum[i - 1][0] + g[i][0]; // 当前元素=上一行同一列元素的前缀和+当前元素}// 计算第一行的前缀和,只需逐列累加for (int j = 1; j < m; j++){sum[0][j] = sum[0][j - 1] + g[0][j]; // 当前元素=前一列同一行元素的前缀和+当前元素}// 计算剩余部分的前缀和for (int i = 1; i < n; i++){for (int j = 1; j < m; j++){// 二维前缀和公式:// 当前元素的前缀和 = 当前元素值 + 上一行同一列前缀和 + 当前行前一列前缀和 - 左上角重叠部分的前缀和sum[i][j] = g[i][j] + sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];}}
}// 获取子矩阵的元素和,(x1, y1)为左上角坐标,(x2, y2)为右下角坐标
int get_sum(int x1, int y1, int x2, int y2)
{// 如果左上角坐标是(0,0),直接返回sum[x2][y2]的值if (!x1 && !y1){return sum[x2][y2];}// 如果x1是0,说明子矩阵的上边界在第一行// 结果为整个矩形的前缀和减去子矩阵左侧部分的前缀和if (!x1){return sum[x2][y2] - sum[x2][y1 - 1];}// 如果y1是0,说明子矩阵的左边界在第一列// 结果为整个矩形的前缀和减去子矩阵上方部分的前缀和if (!y1){return sum[x2][y2] - sum[x1 - 1][y2];}// 其他情况,返回整个矩形的前缀和,减去子矩阵左侧和上方的前缀和,并加上左上角重叠部分的前缀和return sum[x2][y2] - sum[x2][y1 - 1] - sum[x1 - 1][y2] + sum[x1 - 1][y1 - 1];
}int main()
{pre_sum(); // 先计算前缀和// 输出从(1, 1)到(2, 2)的子矩阵和,以及从(0, 1)到(1, 3)的子矩阵和cout << get_sum(1, 1, 2, 2) << " " << get_sum(0, 1, 1, 3);return 0; // 程序结束
}

二维差分

有一个二维数组我们对某个区间内的所有元素进行加减操作,如下图所示
在这里插入图片描述
我们在一维数组中操作的时候我们是打标记,在d数组中某个下标的元素+1,他会影响到后面的所有元素,那么假如说我们在二维数组中的二维数组d中某个元素+1会影响那些元素呢?
在这里插入图片描述
我们可以发现在二维数组中我们需要打四个标记通过这个图我们可以写出这样的公式,我们对一个初始化为0的数组d进行操作,根据差分标记来改变数组d,然后对数组d进行前缀和,得到前缀和数组sumd,最后将前缀和数组加到原来的数组即可。
在这里插入图片描述

代码如下

#include<iostream> // 包含输入输出流的头文件
using namespace std; // 使用标准命名空间const int n = 3, m = 4; // 定义二维数组的行数n和列数m
int g[n][m] = { {1,5,6,8},{9,6,7,3},{5,3,2,4} }; // 初始化二维数组g,用于存储原始矩阵
int sum[n][m]; // 用于存储前缀和的二维数组
int d[n + 1][m + 1] = { 0 }; // 定义二维差分数组d,多一行一列以处理边界问题// 计算前缀和并更新g数组
void pre_sum()
{sum[0][0] = d[0][0]; // 左上角的前缀和为差分数组d的值// 计算第一列的前缀和,只需逐行累加for (int i = 1; i < n; i++){sum[i][0] = sum[i - 1][0] + d[i][0];}// 计算第一行的前缀和,只需逐列累加for (int j = 1; j < m; j++){sum[0][j] = sum[0][j - 1] + d[0][j];}// 计算其余部分的前缀和for (int i = 1; i < n; i++){for (int j = 1; j < m; j++){// 二维前缀和公式sum[i][j] = d[i][j] + sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];}}// 将差分作用应用到原始矩阵g上for (int i = 0; i < n; i++){for (int j = 0; j < m; j++){g[i][j] += sum[i][j]; // 更新g[i][j]为修改后的值}}
}// 输出矩阵g
void print()
{for (int i = 0; i < n; i++) // 遍历每一行{for (int j = 0; j < m; j++) // 遍历每一列{cout << g[i][j] << " "; // 输出g矩阵中的元素}cout << endl; // 换行}
}// 获取子矩阵的元素和,(x1, y1)为左上角坐标,(x2, y2)为右下角坐标
int get_sum(int x1, int y1, int x2, int y2)
{if (!x1 && !y1){return sum[x2][y2]; // 如果子矩阵的左上角是(0,0),直接返回sum[x2][y2]}if (!x1){return sum[x2][y2] - sum[x2][y1 - 1]; // 如果子矩阵在第一行,减去左侧部分的前缀和}if (!y1){return sum[x2][y2] - sum[x1 - 1][y2]; // 如果子矩阵在第一列,减去上方部分的前缀和}// 其他情况,使用前缀和公式计算子矩阵的和return sum[x2][y2] - sum[x2][y1 - 1] - sum[x1 - 1][y2] + sum[x1 - 1][y1 - 1];
}// 在差分数组d上应用区间更新
void add(int x1, int y1, int x2, int y2, int val)
{// 根据二维差分数组的性质,更新四个点d[x1][y1] += val;          // 左上角增加vald[x2 + 1][y1] -= val;      // 下方减去vald[x1][y2 + 1] -= val;      // 右侧减去vald[x2 + 1][y2 + 1] += val;  // 右下角加上val
}// 输出差分数组d
void printD()
{for (int i = 0; i < n + 1; i++) // 遍历差分数组的行{for (int j = 0; j < m + 1; j++) // 遍历差分数组的列{cout << d[i][j] << " "; // 输出d矩阵中的元素}cout << endl; // 换行}
}int main()
{// 调用add函数,进行两次区间更新add(0, 0, 2, 1, 3);  // 在区域(0,0)到(2,1)增加3add(1, 1, 2, 2, -1); // 在区域(1,1)到(2,2)减少1// printD(); // 如果想调试差分数组d的状态,可以取消注释这一行pre_sum(); // 计算前缀和并更新矩阵gprint();   // 输出更新后的矩阵greturn 0; // 程序结束
}

二位前缀和与二维差分源码


http://www.ppmy.cn/news/1528247.html

相关文章

下载 B 站封面的正确方式

B 友们经常用一些很好看的图片作为视频封面&#xff0c;但是大部分都不会指出图片来源&#xff0c;为此我们可以下载封面原图&#xff0c;用于保存或者搜索源出处。 这里介绍几个我知道的方法&#xff0c;欢迎补充&#x1f914; ‍ 使用相关客户端 上一篇博客介绍了很多的能…

【C++篇】~类和对象(中)

类和对象&#xff08;中&#xff09; 1.类的默认成员函数​ 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。一个类&#xff0c;我们不写的情况下编译器会默认生成以下6个默认成员函数&#xff0c;需要注意的是这6个中最重要的是前…

k8s证书过期处理

证书一共分为 根CA&#xff08;ca.crt&#xff09; master各组件的证书&#xff08;包括etcd、apiserver、front-proxy、controller-manager等各种&#xff09; kubelet证书 k8s证书有效期说明&#xff1a; 1、原生版本有效期master节点&#xff1a; /etc/kubernetes/ssl/…

Node js介绍

目录 概要**对Node的认识****Node的概念理解****Node和浏览器区别****Node的架构图** **Node的应用场景****Node的安装****安装Node的LTS版本****Node的版本管理工具nvm(了解)** **Node的输入和输出**Node程序传递参数Node的输出 **Node的全局对象****特殊的全局对象****其他的…

[ffmpeg] 视频格式转换

本文主要梳理 ffmpeg 中的视频格式转换。由于上屏的数据是 rgba&#xff0c;编码使用的是 yuv数据&#xff0c;所以经常会使用到视频格式的转换。 除了使用 ffmpeg进行转换&#xff0c;还可以通过 libyuv 和 directX 写 shader 进行转换。 之前看到文章说 libyuv 之前是 ffmpeg…

2024新动态:低代码开发占领新常态市场

随着技术的不断进步和数字化转型的加速&#xff0c;企业对于快速开发和部署应用程序的需求日益增长。2024年&#xff0c;低代码开发平台已经成为新常态市场的重要力量&#xff0c;它通过简化应用程序的开发过程&#xff0c;让非技术背景的业务用户也能参与到软件开发中来&#…

springboot结合p6spy进行SQL监控

1.学习p6spy的相关链接 英文文档&#xff1a;Integrating P6Spy — p6spy 3.9.2-SNAPSHOT documentationhttps://p6spy.readthedocs.io/en/latest/integration.html github链接&#xff1a;GitHub - p6spy/p6spy: P6Spy is a framework that enables database data to be sea…

rancher 图形化界面

概念 rancher就是图形化界面进行k8s集群的管理。它自带普罗米修斯监控 安装rancher 在三台节点主机上把rancher包拖进去 docker load -i rancher.tar 在master主节点上 docker pull rancher/rancher:v2.5.7 docker run -d --restartunless-stopped -p 80:80 -p 443:443 …