这段代码实现了一个二维前缀和的计算和查询功能。它的核心思想是通过预先计算一个二维前缀和数组 s
,使得每次查询某个子矩阵的和时,可以在常数时间内完成。以下是代码的详细思路解析:
1. 问题背景
假设有一个二维数组 a
,大小为 n × m
,我们需要多次查询某个子矩阵的和。直接计算子矩阵和的方法是遍历子矩阵内的所有元素并累加,但这会导致每次查询的时间复杂度为 O(n × m),效率较低。为了优化查询效率,可以使用二维前缀和技术。
2. 二维前缀和的概念
二维前缀和数组 s
是一个辅助数组,其中 s[i][j]
表示从 (1, 1)
到 (i, j)
的子矩阵内所有元素的累加和,即:
cpp复制
s[i][j] = sum(a[x][y]) for all 1 ≤ x ≤ i and 1 ≤ y ≤ j
通过二维前缀和数组,可以快速计算任意子矩阵 (x1, y1)
到 (x2, y2)
的和:
cpp复制
sum(x1, y1, x2, y2) = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]
这里的关键是利用了二维前缀和的性质,通过加减四个部分的和来得到目标子矩阵的和。
3. 代码逻辑解析
(1) 输入部分
cpp复制
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
-
用户输入矩阵的行数
n
、列数m
和查询次数q
。 -
接着输入矩阵
a
的每个元素。矩阵a
的索引从(1, 1)
开始,方便后续计算。
(2) 二维前缀和数组的计算
cpp复制
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
-
使用双重循环计算二维前缀和数组
s
:-
s[i][j]
的值等于:-
s[i - 1][j]
:上一行的前缀和。 -
s[i][j - 1]
:左边列的前缀和。 -
- s[i - 1][j - 1]
:减去重复计算的部分(左上角的前缀和)。 -
+ a[i][j]
:加上当前元素的值。
-
-
-
这样,
s[i][j]
就存储了从(1, 1)
到(i, j)
的子矩阵内所有元素的累加和。
(3) 查询部分
cpp复制
while (q--)
{
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] << endl;
}
-
用户输入查询的子矩阵范围
(x1, y1)
到(x2, y2)
。 -
利用二维前缀和数组快速计算子矩阵的和:
-
s[x2][y2]
:整个大矩阵的前缀和。 -
- s[x1 - 1][y2]
:减去上方多余部分的前缀和。 -
- s[x2][y1 - 1]
:减去左侧多余部分的前缀和。 -
+ s[x1 - 1][y1 - 1]
:加上被重复减去的部分(左上角的前缀和)。
-
-
输出查询结果。
4. 代码效率分析
-
时间复杂度:
-
计算二维前缀和数组的时间复杂度为 O(n × m)。
-
每次查询的时间复杂度为 O(1)。
-
总时间复杂度为 O(n × m + q),其中
n × m
是矩阵大小,q
是查询次数。
-
-
空间复杂度:
-
使用了一个额外的二维前缀和数组
s
,空间复杂度为 O(n × m)。
-
5. 示例运行
输入:
plaintext复制
3 3 2
1 2 3
4 5 6
7 8 9
1 1 2 2
2 2 3 3
运行过程:
-
输入矩阵大小
n = 3
,m = 3
和查询次数q = 2
。 -
输入矩阵
a
:复制
1 2 3 4 5 6 7 8 9
-
计算二维前缀和数组
s
:复制
s[0][0] = 0 s[1][1] = 1 s[1][2] = 3 s[1][3] = 6 s[2][1] = 5 s[2][2] = 12 s[2][3] = 21 s[3][1] = 12 s[3][2] = 27 s[3][3] = 45
-
处理查询:
-
查询
(1, 1)
到(2, 2)
的和:cpp复制
s[2][2] - s[0][2] - s[2][0] + s[0][0] = 12 - 0 - 0 + 0 = 12
-
查询
(2, 2)
到(3, 3)
的和:cpp复制
s[3][3] - s[1][3] - s[3][1] + s[1][1] = 45 - 6 - 12 + 1 = 28
-
输出:
plaintext复制
12
28
6. 总结
这段代码的核心思路是通过二维前缀和技术,将子矩阵和查询的时间复杂度从 O(n × m) 优化到 O(1)。通过预先计算一个二维前缀和数组 s
,可以快速完成多次子矩阵和查询,适用于需要频繁查询子矩阵和的场景。
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m, q;
int a[N][N], s[N][N];
int main()
{
cin>>n>>m>>q;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
cin>>a[i][j];
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
while(q--)
{
int x1, y1, x2, y2;
cin>>x1>>y1>>x2>>y2;
cout<<s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] << endl;
}
return 0;
}