「学习笔记」二维树状数组

本文算是水文......
二维树状数组解释起来太麻烦了,但写法和一维几乎差不多,就是加了一层循环,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,j1)+sum(i1,j)sum(i1,j1)+d(i,j)
再推下去 d(i,j)=a(i,j)sum(i,j1)sum(i1,j)+sum(i1,j1)
又因为 sum(i,j)=a(i,j)
所以 d(i,j)=a(i,j)a(i,j1)a(i1,j)+a(i1,j1)
按照二维差分的修改:让 (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;
}

区间修改,区间查询:
跟一维的公式解释差不多,区间查询,实际就是求 i=1xj=1yh=1ik=1j (dh,k)

i=1xj=1yh=1ik=1j (dh,k)=i=1xj=1y(di,j×(x+1i)×(y+1j))

继续往下推

i=1xj=1y(di,j×(x+1i)×(y+1j))=(x+1i)×(y+1)×i=1xj=1y(di,j)(j×(x+1i)×i=1xj=1y(di,j))=(x+1)×(y+1)×i=1xj=1y(di,j)(y+1)×i×i=1xj=1y(di,j)(x+1)×j×i=1xj=1y(di,j)+i×j×i=1xj=1y(di,j)

因此,我们只要维护 i=1xj=1y(di,j)i=1xj=1y(di,j)×ii=1xj=1y(di,j)×ji=1xj=1y(di,j)×i×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;
}

作者:yifan0305

出处:https://www.cnblogs.com/yifan0305/p/16426324.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

转载时还请标明出处哟!

posted @   yi_fan0305  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示