Learn how to efficiently calculate the sum of any submatrix in constant time using 2D prefix sums. This technique is crucial for solving matrix-based problems in competitive programming.
In this tutorial, I'm going to describe a problem and a simple solution to solve this to learn two dimensional cumulative sums.
You have a 2D matrix mat
having n rows and m columns.
You have some assigned numbers in every cell.
You need to do some queries, in every query you need to print the sum of the submatrix. You will be given the position of upper left cell and lower right cell of the matrix.
Col 1 | Col 2 | Col 3 | Col 4 | Col 5 | Col 6 | |
---|---|---|---|---|---|---|
Row 1 | 3 | 5 | 1 | 16 | 8 | 13 |
Row 2 | 12 | 2 | 13 | 9 | 11 | 6 |
Row 3 | 7 | 10 | 6 | 12 | 4 | 7 |
Row 4 | 11 | 6 | 17 | 4 | 7 | 18 |
Row 5 | 8 | 14 | 7 | 8 | 12 | 3 |
Query Example: Find the sum of submatrix from (2,3) to (4,5) - highlighted cells above.
Let's solve this problem naively first. We can run a simple nested loop to find the sum:
int sum(int x1, int y1, int x2, int y2) {
int sum = 0;
for (int i = x1; i <= x2; i++) {
for (int j = y1; j <= y2; j++) {
sum += mat[i][j];
}
}
return sum;
}
Let's solve this problem efficiently using 2D prefix sums. We'll store
the cumulative sum of the matrix in such a way that cell(i, j)
will have cumulative sum of the submatrix having upper left
cell(1, 1)
and lower right cell(i, j)
.
Let's have another 2D matrix csum
for storing the
cumulative sum of the matrix mat
.
csum[i][j] = mat[i][j] + csum[i-1][j] + csum[i][j-1] - csum[i-1][j-1]
void calc(int n, int m) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
csum[i][j] = mat[i][j] + csum[i-1][j] +
csum[i][j-1] - csum[i-1][j-1];
}
}
}
To find the sum of submatrix from (x1, y1)
to
(x2, y2)
, we use the inclusion-exclusion principle:
sum = csum[x2][y2] - csum[x2][y1-1] - csum[x1-1][y2] + csum[x1-1][y1-1]
int query(int x1, int y1, int x2, int y2) {
return csum[x2][y2] - csum[x2][y1-1] -
csum[x1-1][y2] + csum[x1-1][y1-1];
}
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
typedef pair<ll, ll> pll;
const int Max = 2e3 + 10;
const int Mod = 1e9 + 7;
const ll Inf = 1LL << 62;
int mat[Max][Max];
int csum[Max][Max];
void calc(int n, int m) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
csum[i][j] = mat[i][j] + csum[i-1][j] +
csum[i][j-1] - csum[i-1][j-1];
}
}
}
int query(int x1, int y1, int x2, int y2) {
return csum[x2][y2] - csum[x2][y1-1] -
csum[x1-1][y2] + csum[x1-1][y1-1];
}
int main(int argc, char const *argv[]) {
int n, m, q, x1, y1, x2, y2;
cin >> n >> m >> q;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> mat[i][j];
}
}
calc(n, m);
while (q--) {
cin >> x1 >> y1 >> x2 >> y2;
cout << query(x1, y1, x2, y2) << endl;
}
return 0;
}
Operation | Time Complexity | Space Complexity |
---|---|---|
Preprocessing | O(n × m) | O(n × m) |
Each Query | O(1) | O(1) |
Overall | O(n × m + q) | O(n × m) |
Powerful technique for range sum queries in 2D arrays
Essential principle for avoiding double counting
After O(n×m) preprocessing, answer queries in constant time
Use extra space to achieve faster query times