二维前缀和与差分、离散化技巧
二维前缀和
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: // 越界就返回 0 int 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); // 矩阵里面全是 0 if (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++) // 如果面积差为周长,说明有一圈 1 if (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,说明洞上并没有邮票 // 此时返回 false if (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 * n vector<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; } };
本文作者:n1ce2cv
本文链接:https://www.cnblogs.com/sprinining/p/18453337
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2021-10-09 主机与安卓模拟器建立连接
2021-10-09 Socket