「学习笔记」二维树状数组
本文算是水文......
二维树状数组解释起来太麻烦了,但写法和一维几乎差不多,就是加了一层循环,So,这篇文章解释会很少几乎没有
update 2022.11.4 我又回来补充点东西
单点修改,区间查询:
这个和一维的相差不大,真的只是多了层循环
#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
ll n,m,p,x,y,z,w;
ll s[4500][4500];
void add(ll,ll,ll);
ll sum(ll,ll);
int main()
{
scanf("%lld%lld",&n,&m);
while((scanf("%lld",&p))!=EOF)
{
if(p==1)
{
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);
}
else
{
scanf("%lld%lld%lld%lld",&w,&x,&y,&z);
printf("%lld\n",sum(y,z)-sum(w-1,z)-sum(y,x-1)+sum(w-1,x-1));
}
}
return 0;
}
void add(ll a,ll b,ll c)
{
for(rll i=a;i<=n;i+=(i&(-i)))
{
for(rll j=b;j<=m;j+=(j&(-j)))
{
s[i][j]+=c;
}
}
}
ll sum(ll a,ll b)
{
ll ans=0;
for(rll i=a;i;i-=(i&(-i)))
{
for(rll j=b;j;j-=(j&(-j)))
{
ans+=s[i][j];
}
}
return ans;
}
区间修改,单点查询:
区间修改还是差分的思想,只不过这个差分是二维的
设差分数组为 \(d(i, j)\),原数组为 \(a(i, j)\),差分数组的前缀和为 \(sum(i, j)\)
根据差分与前缀和的关系,我们可以知道 \(a(i, j) = sum(i, j - 1) + sum(i - 1, j) - sum(i - 1, j - 1) + d(i, j)\)
再推下去 \(d(i, j) = a(i, j) - sum(i, j - 1) - sum(i - 1, j) + sum(i - 1, j - 1)\)
又因为 \(sum(i, j) = a(i, j)\)
所以 \(d(i, j) = a(i, j) - a(i, j - 1) - a(i - 1, j) + a(i - 1, j - 1)\)
按照二维差分的修改:让 \((i, j)-(h, k)\) 的区域都 \(+k\),只需要在 \(d(i, j)\) 位置和 \(d(h + 1, k + 1) + k\) ,在 \(d(h + 1, j)\) 和 \(d(i, k + 1) - k\) 即可,单点查询的做法等同于上面的区间查询
代码:
#include <bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
ll n, m, a, b, c, d, k, x, y, p;
ll s[4500][4500];
void add(ll, ll, ll);
ll sum(ll, ll);
int main() {
scanf("%lld%lld", &n, &m);
while ((scanf("%lld", &p)) != EOF) {
if (p == 1) {
scanf("%lld%lld%lld%lld%lld", &a, &b, &c, &d, &k);
add(a, b, k);
add(a, d + 1, -k);
add(c + 1, b, -k);
add(c + 1, d + 1, k);
}
if (p == 2) {
scanf("%lld%lld", &x, &y);
printf("%lld\n", sum(x, y));
}
}
return 0;
}
void add(ll xx, ll yy, ll zz) {
for (rll i = xx; i <= n; i += (i & (-i))) {
for (rll j = yy; j <= m; j += (j & (-j))) {
s[i][j] += zz;
}
}
}
ll sum(ll xx, ll yy) {
ll ans = 0;
for (rll i = xx; i; i -= (i & (-i))) {
for (rll j = yy; j; j -= (j & (-j))) {
ans += s[i][j];
}
}
return ans;
}
区间修改,区间查询:
跟一维的公式解释差不多,区间查询,实际就是求 \(\sum_{i=1}^{x} \sum _{j=1}^{y} \sum_{h=1}^{i} \sum_{k=1}^j \ (d_{h, k})\)
继续往下推
因此,我们只要维护 \(\sum_{i=1}^{x} \sum_{j=1}^{y}(d_{i, j})、\sum_{i=1}^{x} \sum_{j=1}^{y}(d_{i, j}) \times i、\sum_{i=1}^{x} \sum_{j=1}^{y}(d_{i, j}) \times j、\sum_{i=1}^{x} \sum_{j=1}^{y}(d_{i, j}) \times i \times j\) 即可
代码:
#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
ll n,m;
ll s1[2400][2400],s2[2400][2400],s3[2400][2400],s4[2400][2400];
void add(ll,ll,ll);
ll sum(ll,ll);
int main()
{
ll p,a,b,c,d,x;
scanf("%lld%lld",&n,&m);
while((scanf("%lld",&p))!=EOF)
{
if(p==1)
{
scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&x);
add(a,b,x);
add(a,d+1,-x);
add(c+1,b,-x);
add(c+1,d+1,x);
}
else
{
scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
printf("%lld\n",sum(c,d)-sum(c,b-1)-sum(a-1,d)+sum(a-1,b-1));
}
}
return 0;
}
void add(ll x,ll y,ll z)
{
for(rll i=x;i<=n;i+=(i&(-i)))
{
for(rll j=y;j<=m;j+=(j&(-j)))
{
s1[i][j]+=z;
s2[i][j]+=z*x;
s3[i][j]+=z*y;
s4[i][j]+=z*x*y;
}
}
}
ll sum(ll x,ll y)
{
ll ans=0;
for(rll i=x;i;i-=(i&(-i)))
{
for(rll j=y;j;j-=(j&(-j)))
{
ans+=((x+1)*(y+1)*s1[i][j])-((y+1)*s2[i][j])-((x+1)*s3[i][j])+s4[i][j];
}
}
return ans;
}