二维树状数组

前置知识:树状数组学习

二维树状数组简介

二维树状数组用于处理二维数组中的查询和修改。和一维树状数组一样,二维树状数组代码短,常数和空间小,时间复杂度小,十分方便好用。

设原数组上的点为 a [ i ] [ j ] a[i][j] a[i][j],二维树状数组上的点为 s [ i ] [ j ] s[i][j] s[i][j],则

s [ x ] [ y ] = ∑ i = t x x ∑ j = t y y a [ i ] [ j ] s[x][y]=\sum\limits_{i=tx}^x\sum\limits_{j=ty}^y a[i][j] s[x][y]=i=txxj=tyya[i][j]

其中 t x = x − 2 m + 1 tx=x-2^m+1 tx=x2m+1 t y = y − 2 n + 1 ty=y-2^n+1 ty=y2n+1
m m m表示 x x x的二进制中末尾连续的0的个数, n n n表示 y y y的二进制中末尾连续的0的个数。


二维树状数组的操作

单点修改

和一维树状数组类似,修改 a [ i ] [ j ] a[i][j] a[i][j],要将所有含有 a [ i ] [ j ] a[i][j] a[i][j] s [ i ] [ j ] s[i][j] s[i][j]都修改。

code

void add(int x,int y,long long v){
	for(int i=x;i<=n;i+=lb(i)){
		for(int j=y;j<=m;j+=lb(j)){
			tr[i][j]+=v;
		}
	}
}

时间复杂度为 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n)


区间查询

a a a数组的二维前缀和,和一维树状数组的求法类似。

code

long long find(int x,int y){
	long long re=0;
	for(int i=x;i;i-=lb(i)){
		for(int j=y;j;j-=lb(j)){
			re+=tr[i][j];
		}
	}
	return re;
}

若要求 ∑ i = a c ∑ j = b d a [ i ] [ j ] \sum\limits_{i=a}^c\sum\limits_{j=b}^d a[i][j] i=acj=bda[i][j],则答案为 f i n d ( c , d ) − f i n d ( c − 1 , b ) − f i n d ( a − 1 , d ) + f i n d ( a − 1 , b − 1 ) find(c,d)-find(c-1,b)-find(a-1,d)+find(a-1,b-1) find(c,d)find(c1,b)find(a1,d)+find(a1,b1)。可以画图理解。

时间复杂度为 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n)


例题

单点修改+区间查询模板的代码就是上面两个代码拼接起来,这里不再赘述。下面给一道有难度的题。

洛谷 P4514 上帝造题的七分钟

这道题涉及到区间查询和区间修改。

和一维树状数组一样,设 d d d数组为 a a a数组的差分,即 d [ i ] [ j ] = a [ i ] [ j ] − a [ i − 1 ] [ j ] − a [ i ] [ j − 1 ] + a [ i − 1 ] [ j − 1 ] d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1] d[i][j]=a[i][j]a[i1][j]a[i][j1]+a[i1][j1]

a [ x ] [ y ] = ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] a[x][y]=\sum\limits_{i=1}^x\sum\limits_{j=1}^y d[i][j] a[x][y]=i=1xj=1yd[i][j]

对于区间修改,设将 ( a , b ) (a,b) (a,b) ( c , d ) (c,d) (c,d)为顶点的矩形内每个数都增加 v v v,则 d [ c ] [ d ] + = v , d [ c ] [ b − 1 ] − = v , d [ a − 1 ] [ d ] − = v , d [ a − 1 ] [ b − 1 ] + = v d[c][d]+=v,d[c][b-1]-=v,d[a-1][d]-=v,d[a-1][b-1]+=v d[c][d]+=v,d[c][b1]=v,d[a1][d]=v,d[a1][b1]+=v即可,这样就变成了单点修改。

对于区间查询:
s u m [ x ] [ y ] = ∑ i = 1 x ∑ j = 1 y a [ i ] [ j ] sum[x][y]=\sum\limits_{i=1}^x\sum\limits_{j=1}^y a[i][j] sum[x][y]=i=1xj=1ya[i][j]
   = ∑ i = 1 x ∑ j = 1 y ∑ i ′ = 1 i ∑ j ′ = 1 j d [ i ′ ] [ j ′ ] \qquad \qquad \ \ =\sum\limits_{i=1}^x\sum\limits_{j=1}^y\sum\limits_{i'=1}^i\sum\limits_{j'=1}^j d[i'][j']   =i=1xj=1yi=1ij=1jd[i][j]
   = ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ ( x − i + 1 ) ∗ ( y − j + 1 ) \qquad \qquad \ \ =\sum\limits_{i=1}^x\sum\limits_{j=1}^yd[i][j]*(x-i+1)*(y-j+1)   =i=1xj=1yd[i][j](xi+1)(yj+1)
   = ( x + 1 ) ( y + 1 ) ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] − ( y + 1 ) ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ i − ( x + 1 ) ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ j + ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ i ∗ j \qquad \qquad \ \ =(x+1)(y+1)\sum\limits_{i=1}^x\sum\limits_{j=1}^yd[i][j]-(y+1)\sum\limits_{i=1}^x\sum\limits_{j=1}^yd[i][j]*i-(x+1)\sum\limits_{i=1}^x\sum\limits_{j=1}^yd[i][j]*j+\sum\limits_{i=1}^x\sum\limits_{j=1}^yd[i][j]*i*j   =(x+1)(y+1)i=1xj=1yd[i][j](y+1)i=1xj=1yd[i][j]i(x+1)i=1xj=1yd[i][j]j+i=1xj=1yd[i][j]ij

所以,我们需要维护 d [ i ] [ j ] , d [ i ] [ j ] ∗ i , d [ i ] [ j ] ∗ j , d [ i ] [ j ] ∗ i ∗ j d[i][j],d[i][j]*i,d[i][j]*j,d[i][j]*i*j d[i][j],d[i][j]i,d[i][j]j,d[i][j]ij的前缀和,即可求出 a [ i ] [ j ] a[i][j] a[i][j]的和。

code

#include<bits/stdc++.h>
using namespace std;
int n,m,tr[2505][2505][4];
char ch;
int lb(int i){
	return i&(-i);
}
void add(int x,int y,long long v){
	for(int i=x;i<=n;i+=lb(i)){
		for(int j=y;j<=m;j+=lb(j)){
			tr[i][j][0]+=v;
			tr[i][j][1]+=v*x;
			tr[i][j][2]+=v*y;
			tr[i][j][3]+=v*x*y;
		}
	}
}
long long find(int x,int y,int z){
	long long re=0;
	for(int i=x;i;i-=lb(i)){
		for(int j=y;j;j-=lb(j)){
			re+=tr[i][j][z];
		}
	}
	return re;
}
long long sum(int x,int y){
	return (x+1)*(y+1)*find(x,y,0)-(y+1)*find(x,y,1)-(x+1)*find(x,y,2)+find(x,y,3);
}
int main()
{
	int a,b,c,d;
	long long v;
	scanf("%c%d%d",&ch,&n,&m);
	while(scanf("%c",&ch)!=EOF){
		if(ch!='L'&&ch!='k') continue;
		if(ch=='L'){
			scanf("%d%d%d%d%d",&a,&b,&c,&d,&v);
			add(a,b,v);
			add(c+1,b,-v);
			add(a,d+1,-v);
			add(c+1,d+1,v);
		}
		else{
			scanf("%d%d%d%d",&a,&b,&c,&d);
			printf("%d\n",sum(c,d)-sum(c,b-1)-sum(a-1,d)+sum(a-1,b-1));
		}
	}
	return 0;
}
  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值