【一百零四】【算法分析与设计】【模板】二维差分,2132. 用邮票贴满网格图,LCP 74. 最强祝福力场,二位差分,差分思想,记录变化值,离散化技巧

本文主要是介绍【一百零四】【算法分析与设计】【模板】二维差分,2132. 用邮票贴满网格图,LCP 74. 最强祝福力场,二位差分,差分思想,记录变化值,离散化技巧,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【模板】二维差分

描述

给你一个n行m列的矩阵,下标从1开始。

接下来有q次操作,每次操作输入5个参数x1, y1, x2, y2, k

表示把以(x1, y1)为左上角,(x2,y2)为右下角的子矩阵的每个元素都加上k,

请输出操作后的矩阵。

输入描述:

第一行包含三个整数n,m,q.

接下来n行,每行m个整数,代表矩阵的元素

接下来q行,每行5个整数x1, y1, x2, y2, k,分别代表这次操作的参数

1 ≤ n , m ≤ 1000 1\le n,m \le 1000 1n,m1000

1 ≤ q ≤ 1 0 5 1\le q \le 10^5 1q105

1 ≤ x 1 ≤ x 2 ≤ n 1\le x1 \le x2 \le n 1x1x2n

1 ≤ y 1 ≤ y 2 ≤ m 1\le y1 \le y2 \le m 1y1y2m

− 1 0 9 ≤ 矩阵中的元素 ≤ 1 0 9 -10^9 \le 矩阵中的元素 \le 10^9 109矩阵中的元素109

输出描述:

输出n行,每行m个数,每个数用空格分开,表示这个矩阵。

示例1

输入:

2 3 4

1 2 3

4 5 6

1 1 2 2 3

1 2 2 3 -1

1 1 1 3 4

1 1 2 1 1

复制

输出:

9 8 6

8 7 5

复制

差分思想,存储影响的量,和消除影响的量.

在这里插入图片描述

例如我在A区域全部加上k值.

如果原本的值全是0,那么结果应该如下图所示.

在这里插入图片描述

此时我们只需要存储一部分值即可,如下图所示.

在这里插入图片描述

我们对每一行求前缀和,求出来的结果就是我们想要的答案.

可以理解为,从某个位置开始应该影响,影响的值是K,从某个位置开始影响,影响的值是-K.

前面是影响的值,后面是消除影响的值.

也可以理解为原先有很多的重复的数据,而我们只存储改变量,记录从什么时候开始改变了,改变了多少.

接着我们发现上面的数据仍然有许多的重复的数据,一列的K,和一列的-K.

因此我们只需要存储一部分数据,如下图所示.

在这里插入图片描述

对上面的数据先按照列进行求前缀和,再对列的前缀和求行的前缀和,求出来的就是答案.

上面的方法需要遍历两遍diff数组,当然更加常见的是只遍历一遍的diff数组,这个也是可以做到的.

在这里插入图片描述

同样是由这个数据出发,我们需要得到每个数据的列前缀和的行前缀和.

可以利用一点点动态规划的思想,假设我们要得到(i,j)位置的列前缀和的行前缀和值,能不能由其他位置的列前缀和的行前缀和值转移得到?

我们可以先求当前位置的列前缀和,那么就需要用到上一行的列前缀和加上当前元素值.

上一行的列前缀和等于上一行的列前缀和的行前缀和减去上一行上一列的列前缀和的行前缀和.

可以想象我们有一个矩阵全部存储的是列前缀和值.如下图所示.

在这里插入图片描述

假设我要求B位置的值,只需要用蓝色区域前缀和减去绿色区域前缀和即可.

也就是列前缀和的行前缀和相减.

用公式来表示,diff[i-1][j]-diff[i-1][j-1]+g[i][j]这样得到的就是当前位置的列前缀和.

然后再对当前位置列前缀和求行前缀和,只需要加上上一列的列前缀和的行前缀和即可.

用公式来表示,diff[i-1][j]-diff[i-1][j-1]+g[i][j]+diff[i][j-1]这样计算得到的就是当前位置的列前缀和的行前缀和.

填写(i,j)位置需要用到i-1,j-1位置的状态,所以填表顺序i,j从小到大即可.

#include<bits/stdc++.h>
using namespace std;#define int long long
#define endl '\n'
#define fast() ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)#define p pair<int,int>
#define ff first
#define ss second
#define _(i,a,b) for(int i=a;i<=b;i++)
#define _1(i,a,b) for(int i=a;i>=b;i--)int n, m, q;
vector<vector<int>>g;  // 用于存储矩阵元素的二维向量
struct node {int x1, y1;int x2, y2;int k;
};
vector<node>readd;  // 存储每次操作的参数
vector<vector<int>>diff;  // 存储每次操作导致的变化量void sett(int x1, int y1, int x2, int y2, int k) {diff[x1][y1] += k;  // 左上角元素增加kdiff[x1][y2 + 1] -= k;  // 右上角元素增加kdiff[x2 + 1][y1] -= k;  // 左下角元素增加kdiff[x2 + 1][y2 + 1] += k;  // 右下角元素增加k
}void solve() {for (auto& xx : readd) {sett(xx.x1, xx.y1, xx.x2, xx.y2, xx.k);  // 对每次操作进行处理}_(j, 1, m) {  // 对每列进行处理_(i, 1, n) {  // 对每行进行处理diff[i][j] = diff[i - 1][j] + diff[i][j];  // 更新当前元素的值}}_(i, 1, n) {_(j, 1, m) {diff[i][j] = diff[i][j - 1] + diff[i][j];  g[i][j] += diff[i][j];  // 更新矩阵元素的值cout << g[i][j] << " ";  // 输出当前元素值}cout << endl;  // 换行}}signed main() {fast();  // 快速IOcin >> n >> m >> q;  // 输入矩阵的行数、列数和操作次数readd.clear();  // 清空操作参数diff.assign(n + 5, vector<int>(m + 5, 0));  // 初始化变化量向量g.assign(n + 5, vector<int>(m + 5, 0));  // 初始化矩阵向量_(i, 1, n) {_(j, 1, m) {cin >> g[i][j];  // 输入矩阵元素}}_(i, 1, q) {node tt;cin >> tt.x1 >> tt.y1 >> tt.x2 >> tt.y2 >> tt.k;  // 输入每次操作的参数readd.push_back(tt);  // 存储操作参数}solve();  // 解决问题
}

2132. 用邮票贴满网格图

给你一个 m x n 的二进制矩阵 grid ,每个格子要么为 0 (空)要么为 1 (被占据)。

给你邮票的尺寸为 stampHeight x stampWidth 。我们想将邮票贴进二进制矩阵中,且满足以下 限制要求

  1. 覆盖所有 格子。

  2. 不覆盖任何 被占据 的格子。

  3. 我们可以放入任意数目的邮票。

  4. 邮票可以相互有 重叠 部分。

  5. 邮票不允许 旋转

  6. 邮票必须完全在矩阵

如果在满足上述要求的前提下,可以放入邮票,请返回 true ,否则返回 false

示例 1:

在这里插入图片描述

输入: grid = [[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0]], stampHeight = 4, stampWidth = 3 输出: true 解释: 我们放入两个有重叠部分的邮票(图中标号为 1 和 2),它们能覆盖所有与空格子。

示例 2:

在这里插入图片描述

输入: grid = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], stampHeight = 2, stampWidth = 2 输出: false 解释: 没办法放入邮票覆盖所有的空格子,且邮票不超出网格图以外。

提示:

  • m == grid.length

  • n == grid[r].length

  • 1 <= m, n <= 10(5)

  • 1 <= m * n <= 2 * 10(5)

  • grid[r][c] 要么是 0 ,要么是 1

  • 1 <= stampHeight, stampWidth <= 10(5)

在这里插入图片描述

1表示的是不能放邮票的位置,0表示可以放邮票的位置,而邮票的长宽是固定的,而且邮票不能旋转.

那么可以遍历所有的点,找到此时对于的邮票的左上角点x1,y1,右下角点x2,y2.

计算这个矩形区域里面的累加和,如果累加和是0说明可以放邮票,如果累加和不为0说明不能放邮票.

题目要求我们判断能不能将所有0位置都覆盖上邮票.我们不能,放置邮票之后修改0为1,因为邮票是可以叠在一起的,而是要记录所有盖上邮票的位置,然后比对所有0的位置是否被邮票覆盖.

因此我们可以在放置邮票的位置全部累加1,这个累加值是在一个新的数组上进行,然后遍历所有位置,如果是0就看对应位置是否为0,不为0说明被邮票覆盖了,为0说明没有被覆盖过.

而对一个二维区域全部累加1就可以用差分思维.

class Solution {
public:vector<vector<int>> g;    // 二进制矩阵int height, width;        // 邮票的高度和宽度bool flag;                // 是否能成功放置邮票的标志vector<vector<int>> prev; // 原始矩阵的前缀和int n, m;                 // 矩阵的行数和列数vector<vector<int>> diff; // 差分数组,用于辅助计算// 更新差分数组void sett(int x1, int y1, int x2, int y2) {diff[x1][y1] += 1;diff[x1][y2 + 1] -= 1;diff[x2 + 1][y1] -= 1;diff[x2 + 1][y2 + 1] += 1;}// 获取差分数组区间和int gett(int x1, int y1, int x2, int y2) {return prev[x2][y2] - (x1 - 1 >= 0 ? prev[x1 - 1][y2] : 0) -(y1 - 1 >= 0 ? prev[x2][y1 - 1] : 0) +(x1 - 1 >= 0 && y1 - 1 >= 0 ? prev[x1 - 1][y1 - 1] : 0);}// 解决问题的函数void solve() {n = g.size();m = g[0].size();diff.assign(n + 5, vector<int>(m + 5, 0));prev = g;flag = false;// 计算原始矩阵的前缀和for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {prev[i][j] =(i - 1 >= 0 ? prev[i - 1][j] : 0) +(j - 1 >= 0 ? prev[i][j - 1] : 0) -(i - 1 >= 0 && j - 1 >= 0 ? prev[i - 1][j - 1] : 0) +prev[i][j];}}// 尝试在所有可能的位置放置邮票for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {int x1 = i, y1 = j;int x2 = i + height - 1, y2 = j + width - 1;if(y2>=m)break;if(x2>=n)goto f1;if (gett(x1, y1, x2, y2) == 0) {sett(x1, y1, x2, y2);}}}f1:// 计算差分数组的行累加和for (int j = 0; j < m; j++) {for (int i = 0; i < n; i++) {diff[i][j] += (i - 1 >= 0 ? diff[i - 1][j] : 0);}}// 计算差分数组的列累加和for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {diff[i][j] += (j - 1 >= 0 ? diff[i][j - 1] : 0);}}// 检查是否所有空格子都被覆盖for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if (g[i][j] == 0) {if (diff[i][j] == 0) {flag = false;return;}}}}flag = true;}// 判断是否可以成功放置邮票的函数bool possibleToStamp(vector<vector<int>>& _grid, int _stampHeight,int _stampWidth) {g = _grid, height = _stampHeight, width = _stampWidth;ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);solve();  // 解决问题return flag;  // 返回结果}
};

LCP 74. 最强祝福力场

小扣在探索丛林的过程中,无意间发现了传说中“落寞的黄金之都”。而在这片建筑废墟的地带中,小扣使用探测仪监测到了存在某种带有「祝福」效果的力场。 经过不断的勘测记录,小扣将所有力场的分布都记录了下来。forceField[i] = [x,y,side] 表示第 i 片力场将覆盖以坐标 (x,y) 为中心,边长为 side 的正方形区域。

若任意一点的 力场强度 等于覆盖该点的力场数量,请求出在这片地带中 力场强度 最强处的 力场强度

注意:

  • 力场范围的边缘同样被力场覆盖。

示例 1:

输入: forceField = [[0,0,1],[1,0,1]]

输出:2

解释:如图所示,(0.5, 0) 处力场强度最强为 2, (0.5,-0.5)处力场强度同样是 2。

在这里插入图片描述

示例 2:

输入: forceField = [[4,4,6],[7,5,3],[1,6,2],[5,6,3]]

输出:3

解释:如下图所示, forceField[0]、forceField[1]、forceField[3] 重叠的区域力场强度最大,返回 3

在这里插入图片描述

提示:

  • 1 <= forceField.length <= 100

  • forceField[i].length == 3

  • 0 <= forceField[i][0], forceField[i][1] <= 10^9

  • 1 <= forceField[i][2] <= 10^9

每一个辐力区域的辐力值都是1,这些辐力值都是叠加效果.

我们要做的是将每一个矩形区域累加1,然后计算矩形区域最大的值是多少.

重要的并不是矩形区域累加1,因为这个操作用差分就可以完成,重要的是这些点并不能用区域很好的表示,这里就需要用到离散化的技巧.

在这里插入图片描述

假设我们需要用到的点是1,2,6,8,12,1000,正常来说为了表示这些点,需要开辟至少1000大小空间的数组.

但是我们让上面的每一个需要用到的数依次映射012345,这样只需要用到5个数.

关键点是我们需要用到的数必须排序好了.

依次我们可以用map容器,先存储所有需要用到的点放到里面,但是second先不修改.

当我们将所有需要用到的点都放进去了之后,再遍历map依次赋值second为0,1,2…

这样我们只需要关心映射之后的下标值即可,因为这道题目不关心下标,所以也不需要建立0,1,2…映射原下标的操作.

#define LL long longclass Solution {
public:vector<vector<int>> readd; // 记录力场的分布map<LL, LL> x_map;       // 横坐标映射LL n, m;                  // x_map和y_map的大小map<LL, LL> y_map;       // 纵坐标映射LL ret;                   // 最终结果vector<vector<LL>> diff; // 差分数组,用于计算力场强度// 更新差分数组void sett(LL x1, LL y1, LL x2, LL y2) {diff[x1][y1] += 1;diff[x1][y2 + 1] -= 1;diff[x2 + 1][y1] -= 1;diff[x2 + 1][y2 + 1] += 1;}// 解决问题void solve() {n = 0, m = 0;  // 初始化坐标映射大小ret = 0;  // 初始化最终结果为0x_map.clear(), y_map.clear();  // 清空坐标映射// 构建坐标映射for (auto& xx : readd) {x_map[2LL * xx[0] - xx[2]];x_map[2LL * xx[0] + xx[2]];y_map[2LL * xx[1] - xx[2]];y_map[2LL * xx[1] + xx[2]];}LL index = 0;for (auto& xx : x_map) {xx.second = index++;}n = index;index = 0;for (auto& xx : y_map) {xx.second = index++;}m = index;// 初始化差分数组大小diff.assign(n + 5, vector<LL>(m + 5, 0));// 计算力场覆盖情况for (auto& xx : readd) {LL x1 = x_map[2LL * xx[0] - xx[2]];LL x2 = x_map[2LL * xx[0] + xx[2]];LL y1 = y_map[2LL * xx[1] - xx[2]];LL y2 = y_map[2LL * xx[1] + xx[2]];sett(x1, y1, x2, y2);}// 计算力场强度for (LL j = 0; j < m; j++) {for (LL i = 0; i < n; i++) {diff[i][j] += (i - 1 >= 0 ? diff[i - 1][j] : 0);}}for (LL i = 0; i < n; i++) {for (LL j = 0; j < m; j++) {diff[i][j] += (j - 1 >= 0 ? diff[i][j - 1] : 0);ret = max(ret, diff[i][j]);  // 更新最大力场强度}}}// 计算最大力场强度的函数int fieldOfGreatestBlessing(vector<vector<int>>& _forceField) {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);readd = _forceField;  // 获取力场数据solve();  // 解决问题return ret;  // 返回结果}
};

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

这篇关于【一百零四】【算法分析与设计】【模板】二维差分,2132. 用邮票贴满网格图,LCP 74. 最强祝福力场,二位差分,差分思想,记录变化值,离散化技巧的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1031268

相关文章

Python获取中国节假日数据记录入JSON文件

《Python获取中国节假日数据记录入JSON文件》项目系统内置的日历应用为了提升用户体验,特别设置了在调休日期显示“休”的UI图标功能,那么问题是这些调休数据从哪里来呢?我尝试一种更为智能的方法:P... 目录节假日数据获取存入jsON文件节假日数据读取封装完整代码项目系统内置的日历应用为了提升用户体验,

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Spring Boot 配置文件之类型、加载顺序与最佳实践记录

《SpringBoot配置文件之类型、加载顺序与最佳实践记录》SpringBoot的配置文件是灵活且强大的工具,通过合理的配置管理,可以让应用开发和部署更加高效,无论是简单的属性配置,还是复杂... 目录Spring Boot 配置文件详解一、Spring Boot 配置文件类型1.1 applicatio

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

MySQL INSERT语句实现当记录不存在时插入的几种方法

《MySQLINSERT语句实现当记录不存在时插入的几种方法》MySQL的INSERT语句是用于向数据库表中插入新记录的关键命令,下面:本文主要介绍MySQLINSERT语句实现当记录不存在时... 目录使用 INSERT IGNORE使用 ON DUPLICATE KEY UPDATE使用 REPLACE

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

电脑win32spl.dll文件丢失咋办? win32spl.dll丢失无法连接打印机修复技巧

《电脑win32spl.dll文件丢失咋办?win32spl.dll丢失无法连接打印机修复技巧》电脑突然提示win32spl.dll文件丢失,打印机死活连不上,今天就来给大家详细讲解一下这个问题的解... 不知道大家在使用电脑的时候是否遇到过关于win32spl.dll文件丢失的问题,win32spl.dl

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程