二维前缀和与差分、离散化技巧

news/2024/10/9 0:55:11

二维前缀和

304. 二维区域和检索 - 矩阵不可变

二位前缀和目的是预处理出一个结构,以后每次查询二维数组任何范围上的累加和都是 O(1) 的操作

  • 根据原始状况,生成二维前缀和数组sum,

    sum[i][j]: 代表左上角 (0,0) 到右下角 (i,j) 这个范围的累加和

    sum[i][j] += sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1];

  • 查询左上角 (a,b) 到右下角 (c,d) 这个范围的累加和

    sum[c][d] - sum[c][b-1] - sum[a-1][d] + sum[a-1][b-1];

  • 实际过程中往往补第 0 行、第 0 列来减少很多条件判断。

#include <vector>using namespace std;class NumMatrix {
public:vector<vector<int>> sum;NumMatrix(vector<vector<int>> &matrix) {int n = matrix.size();int m = matrix[0].size();// 矩阵扩大,减少边界讨论sum.resize(n + 1, vector<int>(m + 1));// 原始矩阵拷贝到扩大后的矩阵for (int a = 1, c = 0; c < n; a++, c++)for (int b = 1, d = 0; d < m; b++, d++)sum[a][b] = matrix[c][d];// 计算二维前缀和for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++)sum[i][j] += sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1];}int sumRegion(int row1, int col1, int row2, int col2) {row2++;col2++;return sum[row2][col2] - sum[row2][col1] - sum[row1][col2] + sum[row1][col1];}
};

1139. 最大的以 1 为边界的正方形

#include <vector>using namespace std;class Solution {
public:// 越界就返回 0int get(vector<vector<int>> &grid, int i, int j) {return (i < 0 || j < 0) ? 0 : grid[i][j];}// 把原始矩阵变成二位前缀和矩阵void build(int n, int m, vector<vector<int>> &grid) {for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)grid[i][j] += get(grid, i, j - 1) + get(grid, i - 1, j) - get(grid, i - 1, j - 1);}// 返回子矩阵的和int sum(vector<vector<int>> &grid, int a, int b, int c, int d) {return a > c ? 0 : (grid[c][d] - get(grid, c, b - 1) - get(grid, a - 1, d) + get(grid, a - 1, b - 1));}// 时间复杂度 O(n * m * min(n,m)),额外空间复杂度 O(1)int largest1BorderedSquare(vector<vector<int>> &grid) {int n = grid.size();int m = grid[0].size();build(n, m, grid);// 矩阵里面全是 0if (sum(grid, 0, 0, n - 1, m - 1) == 0) return 0;// 找到的最大合法正方形的边长int len = 1;// (a,b) 所有左上角点,(c,d) 更大边长的右下角点,k 是当前尝试的边长for (int a = 0; a < n; a++)for (int b = 0; b < m; b++)// 从 len + 1 找是为了剪枝,只需要找更长的边长for (int c = a + len, d = b + len, k = len + 1; c < n && d < m; c++, d++, k++)// 如果面积差为周长,说明有一圈 1if (sum(grid, a, b, c, d) - sum(grid, a + 1, b + 1, c - 1, d - 1) == (k - 1) << 2)len = k;return len * len;}
};

二维差分

在二维数组中,如果经历如下的过程

  • 批量的做如下的操作,每个操作都有独立的 a、b、c、d、v

void add(a, b, c, d, v) : 左上角 (a,b) 到右下角 (c,d) 范围上,每个数字 +v

// 只对四个点操作
void add(int a, int b, int c, int d, int v) {diff[a][b] += v;diff[c + 1][b] -= v;diff[a][d + 1] -= v;diff[c + 1][d + 1] += v;
}
// 构建二维前缀和
void build() {for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)diff[i][j] += diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1];
}
  • 给矩阵加一圈 0 可以避免边界讨论

【模板】二维差分

#include <iostream>using namespace std;const int MAX_N = 1005;
const int MAX_M = 1005;// 二维差分数组
long long diff[MAX_N][MAX_M];
int n, m, q;// 二维差分,对四个点操作
void add(int a, int b, int c, int d, int k) {diff[a][b] += k;diff[c + 1][b] -= k;diff[a][d + 1] -= k;diff[c + 1][d + 1] += k;
}// 计算二维前缀和
void build() {for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++)diff[i][j] += diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1];
}void clear() {for (int i = 1; i <= n + 1; i++)for (int j = 1; j <= m + 1; j++)diff[i][j] = 0;
}int main() {while (cin >> n >> m >> q) {// 实际矩阵外围有一圈 0,避免边界讨论for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {int value;cin >> value;add(i, j, i, j, value);}}// 二维差分for (int i = 1, a, b, c, d, k; i <= q; i++) {cin >> a >> b >> c >> d >> k;add(a, b, c, d, k);}build();// 打印结果for (int i = 1; i <= n; i++) {cout << diff[i][1];for (int j = 2; j <= m; j++)cout << " " << diff[i][j];cout << endl;}clear();}return 0;
}

P3397 地毯

#include <iostream>using namespace std;const int MAXN = 1002;
int diff[MAXN][MAXN];
int n, q;void add(int a, int b, int c, int d, int k) {diff[a][b] += k;diff[c + 1][b] -= k;diff[a][d + 1] -= k;diff[c + 1][d + 1] += k;
}void build() {for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)diff[i][j] += diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1];
}void clear() {for (int i = 1; i <= n + 1; i++)for (int j = 1; j <= n + 1; j++)diff[i][j] = 0;
}int main() {while (cin >> n >> q) {for (int i = 1, a, b, c, d; i <= q; i++) {cin >> a >> b >> c >> d;add(a, b, c, d, 1);}build();for (int i = 1; i <= n; i++) {cout << diff[i][1];for (int j = 2; j <= n; j++) {cout << " " << diff[i][j];}cout << endl;}clear();}return 0;
}

2132. 用邮票贴满网格图

#include <iostream>
#include <vector>using namespace std;class Solution {
public:void add(vector<vector<int>> &diff, int a, int b, int c, int d) {diff[a][b] += 1;diff[c + 1][d + 1] += 1;diff[c + 1][b] -= 1;diff[a][d + 1] -= 1;}void build(vector<vector<int>> &m) {for (int i = 1; i < m.size(); i++)for (int j = 1; j < m[0].size(); j++)m[i][j] += m[i - 1][j] + m[i][j - 1] - m[i - 1][j - 1];}int sumRegion(vector<vector<int>> &sum, int a, int b, int c, int d) {return sum[c][d] - sum[c][b - 1] - sum[a - 1][d] + sum[a - 1][b - 1];}// 时间复杂度 O(n*m),额外空间复杂度 O(n*m)bool possibleToStamp(vector<vector<int>> &grid, int stampHeight, int stampWidth) {int n = grid.size();int m = grid[0].size();// 前缀和数组vector<vector<int>> prefixSum(n + 1, vector<int>(m + 1));for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)prefixSum[i + 1][j + 1] = grid[i][j];build(prefixSum);// 差分矩阵// 当贴邮票的时候,不再原始矩阵里贴,在差分矩阵里贴// 原始矩阵用来判断能不能贴邮票,不进行修改// 每贴一张邮票都在差分矩阵里修改vector<vector<int>> diff(n + 2, vector<int>(m + 2));// 原始矩阵中 (a,b) 左上角点,根据 stampHeight、stampWidth,算出右下角点(c,d)for (int a = 1, c = a + stampHeight - 1; c <= n; a++, c++)for (int b = 1, d = b + stampWidth - 1; d <= m; b++, d++)// 这个区域彻底都是 0 时,可以贴邮票if (sumRegion(prefixSum, a, b, c, d) == 0)add(diff, a, b, c, d);build(diff);// 检查所有的格子for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)// 原始矩阵里:grid[i][j] == 0,说明是个洞// 差分矩阵里:diff[i + 1][j + 1] == 0,说明洞上并没有邮票// 此时返回 falseif (grid[i][j] == 0 && diff[i + 1][j + 1] == 0)return false;return true;}
};

离散化技巧

LCP 74. 最强祝福力场

#include <iostream>
#include <vector>
#include <algorithm>using namespace std;class Solution {
public:// 排序并去重int mySort(vector<long> &nums) {sort(nums.begin(), nums.end());int size = 1;for (int i = 1; i < nums.size(); i++)if (nums[i] != nums[size - 1])nums[size++] = nums[i];return size;}// 根据数值二分找下标int rank(vector<long> &nums, long v, int size) {int l = 0;int r = size - 1;int m, res = 0;while (l <= r) {m = (l + r) / 2;if (nums[m] >= v) {res = m;r = m - 1;} else {l = m + 1;}}return res + 1;}// 二维差分void add(vector<vector<int>> &diff, int a, int b, int c, int d) {diff[a][b] += 1;diff[c + 1][d + 1] += 1;diff[c + 1][b] -= 1;diff[a][d + 1] -= 1;}// 时间复杂度 O(n^2),额外空间复杂度 O(n^2),n 是力场的个数int fieldOfGreatestBlessing(vector<vector<int>> &forceField) {int n = forceField.size();// n 为矩形的个数,2*n 个坐标vector<long> xs(n << 1);vector<long> ys(n << 1);for (int i = 0, k = 0, p = 0; i < n; i++) {long x = forceField[i][0];long y = forceField[i][1];long r = forceField[i][2];xs[k++] = (x << 1) - r;xs[k++] = (x << 1) + r;ys[p++] = (y << 1) - r;ys[p++] = (y << 1) + r;}int size_x = mySort(xs);int size_y = mySort(ys);// n 个力场,size_x : 2 * n, size_y : 2 * nvector<vector<int>> diff(size_x + 2, vector<int>(size_y + 2));for (int i = 0, a, b, c, d; i < n; i++) {long x = forceField[i][0];long y = forceField[i][1];long r = forceField[i][2];a = rank(xs, (x << 1) - r, size_x);b = rank(ys, (y << 1) - r, size_y);c = rank(xs, (x << 1) + r, size_x);d = rank(ys, (y << 1) + r, size_y);add(diff, a, b, c, d);}int res = 0;// O(n^2)for (int i = 1; i < diff.size(); i++) {for (int j = 1; j < diff[0].size(); j++) {diff[i][j] += diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1];res = max(res, diff[i][j]);}}return res;}
};

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

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

相关文章

KeyShot基础操作2 - 材质篇

介绍了KeyShot的材质相关的内容:上材质、材质参数、贴图类型、映射类型、材质节点图等。​这部分基础操作,只是介绍KeyShot的操作方法,望知晓。 后续也会再更新材质、打光的案例,同时也会提供对应的工程文件。上材质基础操作 材质的通用参数 材质类型 纹理类型 多层材质 贴…

创建进程,设计信号量同步机制,实现多线程同步 - C语言版

环境:Windows11 编译器:Visual Studio 2019相关头文件: #include <windows.h> #include <stdio.h>相关函数:睡眠等待函数:Sleep(int millisecond); 睡眠等待一定时间,会造成OS重新调度其它的线程运行Sleep(10); //当前线程睡眠10毫秒后重新执行创建进程Cre…

Serilog文档翻译系列(七) - 应用设置、调试和诊断、开发接收器

Serilog支持通过App.config和Web.config中的01、应用设置 Serilog 支持在 App.config 和 Web.config 文件中使用简单的 配置语法,以设置最低日志级别、为事件添加额外属性以及控制日志输出。 Serilog 主要通过代码进行配置,设置支持旨在作为补充功能。虽然不是全面的,但大多…

古典+ezRSA

​ 古典密码在线工具:https://ctf.bugku.com/tools.html 一键解码工具库:随波逐流,在github上下载即可 注:古典密码只需做个了解,因为很多都是靠工具实现的,多刷题有个印象,遇到题能看出像什么密码就好。 Base家族 在密码学领域,"base" 通常指的是一种编码方…

【视频讲解】Python量子计算聚类Q-means:量子k-means算法分析电路数据实现可视化

全文链接:https://tecdat.cn/?p=37821 原文出处:拓端数据部落公众号 分析师:Yifan Zhang 量子计算在近期已然成为一个频繁出现的热门概念。尽管它在大众认知以及互联网社区中备受瞩目,热度极高,然而就其实际能力而言,当前仍然存在诸多局限。 量子计算作为一个全新的领域…

20222417 2024-2025-1 《网络与系统攻防技术》实验一实验报告

1.实验内容 (1).掌握反汇编与十六进制编程器 (2).能正确修改机器指令改变程序执行流程 (3).能正确构造payload进行bof攻击 2.实验过程 (1).直接修改程序机器指令,改变程序执行流程 将pwn1文件放入共享文件夹,后续在kali中使用,再将文件复制到实验文件夹share路径…

第一课 php基础语法 变量 函数

php语法<?php// 代码段   ?> php输出方法:echo 和 print不同点:echo-能够输出一个以上的字符串,英文逗号隔开print-只能输出一个字符串,并始终返回1echo 比 print 稍快,并且开销低 注释注释不会被作为程序来读取和执行。它唯一的作用是供代码编辑者阅读(让别人…

CentOS 8 停止维护后通过 rpm 包手动安装 docker

根据 Docker官方文档 的指引,进入 Docker rpm 包下载的地址,根据自己系统的架构和具体版本选择对应的路径这里我使用 https://download.docker.com/linux/centos/7/x86_64/stable 版本,根据 docker 官方的给出的安装命令选择性的下载对应的 rpm 包最终使用 yum 命令安装下载…