题目传送门
题目简述
初始有一个 n×m 的全零矩阵,需要支持两种操作:
矩形加法:将左上角 (a,b)、右下角 (c,d) 的矩形区域内的所有数加上
delta。矩形求和:查询左上角 (a,b)、右下角 (c,d) 的矩形区域内所有数的和。
操作数量较多,需要高效实现,时间复杂度约为 O(lognlogm) 每次操作
思路分析
本题是典型的 二维区间修改 + 区间查询 问题。若直接使用二维树状数组维护原矩阵,区间加法需要修改矩形内所有元素,复杂度不可接受。因此引入 二维差分 将区间修改转化为四个单点修改,而区间查询则通过维护差分数组的加权前缀和来实现。
二维差分
设原矩阵为 a[i][j],其二维差分数组 d[i][j] 定义为:

对矩形 [a..c]×[b..d] 整体加 delta,只需修改差分数组的四个角:
d[a][b] += delta
d[c+1][b] -= delta
d[a][d+1] -= delta
d[c+1][d+1] += delta这样,求一次二维前缀和后,矩形内部的每个元素都会增加 delta,其余位置不变。
前缀和与四个树状数组
我们的目标是快速查询任意矩形和,也就是求原矩阵的二维前缀和:

将 a[i][j] 用差分数组表示并交换求和顺序:

其中 (x−p+1)(y−q+1) 是差分点 (p,q) 影响到的格子数量。展开该乘积:
(x−p+1)(y−q+1)=(x+1)(y+1)−(y+1) p−(x+1) q+p
代入并整理得到:

因此,如果我们能快速求出以下四个量和:
∑d[p][q]
∑d[p][q]⋅p
∑d[p][q]⋅q
∑d[p][q]⋅p⋅q
就可以在 O(lognlogm) 时间内得到前缀和。于是我们维护 四个二维树状数组:
每次更新差分点 (x,y) 加上 d 时,同步更新四个树状数组:
t1加dt2加d * xt3加d * yt4加d * x * y
这样,查询时利用四个树状数组分别求出对应的区间和,套用公式即可。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define maxn 3000
int n, m;
vector<vector<int>> t1(maxn, vector<int>(maxn));
vector<vector<int>> t2(maxn, vector<int>(maxn));
vector<vector<int>> t3(maxn, vector<int>(maxn));
vector<vector<int>> t4(maxn, vector<int>(maxn));
int lowbit(int x) {
return x & (-x);
}
void update(int x, int y, int d) {
for (int i = x; i <= n; i += lowbit(i)) {
for (int j = y; j <= m; j += lowbit(j)) {
t1[i][j] += d, t2[i][j] += d * x;
t3[i][j] += d * y, t4[i][j] += d * y * x;
}
}
}
int sum(int x, int y) {
int h = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
for (int j = y; j > 0; j -= lowbit(j)) {
h += t1[i][j] * (x + 1) * (y + 1) - t2[i][j] * (y + 1) - t3[i][j] * (x + 1) + t4[i][j];
}
}
return h;
}
void solve() {
char X;
cin >> X >> n >> m;
char p;
while (cin >> p) {
if (p == 'L') {
int a, b, c, d, delta;
cin >> a >> b >> c >> d >> delta;
//四个角
update(c + 1, d + 1, delta);
update(a, b, delta);
update(c + 1, b, -delta);
update(a, d + 1, -delta);
}
else {
int a, b, c, d;
cin >> a >> b >> c >> d;
cout << sum(c, d) + sum(a - 1, b - 1) - sum(c, b - 1) - sum(a - 1, d) << endl;
}
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int _ = 1;
//cin >> _;
while (_--) {
solve();
}
return 0;
}总结
本题通过二维差分将区间修改转换为四次单点修改,再利用四个树状数组分别维护差分值及其坐标加权和,实现了高效的二维区间修改与区间查询。
P4514 上帝造题的七分钟(二维树状数组)
http://121.40.154.24:8090/?p=01a06fd8-5270-75ed-866f-c12547adf302
评论