二維字首和
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;
}
};