学习笔记 树状数组

今天闲的蛋疼就复习一下数据结构

写在之前

树状数组是一个好文明

TA可以说是目前维护(O( nlogn ))数据结构当中常数最小的

一般来讲 维护序列的树形数据结构当中

往往是 树状数组 < 线段树 < 平衡树

神级操作

1.区间修改区间查询

树状数组可以说是把差分思想运用到极致的数据结构 没有之一

我们假设

当前序列值为(a_i),维护差分之后就是(d_i)

(a_i = sum_{i=1}^{n}d_i)

那么(sum_{i=1}^{p}a_i=sum_{i=1}^{p}sum_{j=1}^{i}d_i)

由于在这个看似是(O(n^2))的循环中

(d_1)使用了(p)次,(d_2)使用了(p-1)次,(d_3)使用了(p-2)次......(d_p)只被使用了1次

所以很容易总结出来

(sum_{i=1}^{p}a_i=sum_{i=1}^{p}(p-i+1) * d_i =psum_{i=1}^{p}d_i-sum_{i=1}^{p}d_i* (i-1))

所以我们自然就需要维护两个差分数组(d_i)以及(d_i * (i-1))

首先是正常的“建树”

for(int i=1;i<=n;++i)
{
add(tre1,i,num[i]-num[i-1])
add(tre2,i,(i-1)*(num[i]-num[i-1]))
}
然后就是区间修改 依然是差分维护边界

for(int i=1;i<=n;++i)
{
add(tre1,x,z);add(tre1,y+1,-z);
add(tre2,x,(x-1)*z);add(tre2,y+1,(-z)*y);
}
最后就是区间查询 用前缀和求解区间
for(int i=1;i<=n;++i)
{
    最终结果就是
    y*qury(tre1,y)-(x-1)*qury(tre1,x-1)-qury(tre2,y)+qury(tre2,x-1);
}

2.二维树状数组

一道模板提

①.单点修改区域查询

二维前缀和

(s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j])

由于树状数组是维护的前缀和形式

所以到了二维平面上 自然也就是 二维前缀和了

ll tre[3010][3010];
ll n,m,k;
IL void add(ll num,ll x,ll y)
{
	for(R ll i=x;i<=n;i+=(i&-i))
	 for(R ll j=y;j<=m;j+=(j&-j))
	  tre[i][j]+=num;
}
IL ll qury(ll x,ll y)
{
	ll sum=0;
	for(R ll i=x;i;i-=(i&-i))
	 for(R ll j=y;j;j-=(j&-j))
	  sum+=tre[i][j];
    return sum;	  
}
int main()
{
    read(n);read(m);
    for(R ll i=1;i<=n;++i)
     for(R ll j=1;j<=m;++j)
     {
	    ll x;read(x);
	    add(x,i,j);
	 }   
    read(k);
	while(k--)
	{
		ll key,ax,ay,bx,by,z;
		read(key);
		if(key==1)
		{
	        read(ax);read(ay);read(z);
	        add(z,ax,ay);
		}
		else 
		{
			read(ax);read(ay);read(bx);read(by);
			printf("%lld
",qury(bx,by)-qury(bx,ay-1)-qury(ax-1,by)+qury(ax-1,ay-1));
			
		}
	}  
	return 0;
}

②.区域修改单点查询

区间修改还是运用的差分

但是怎么维护差分? ? ? ? ?

还是二维前缀和

(a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+d[i][j])

正常情况下

1 2 3

4 5 6

7 8 9

维护差分之后

1 1 1

3 0 0

3 0 0

单点查询不说

区间修改

譬如说(2,2)到(4,4)

a    a    a    a    a   

a    a+x  a    a    a-y    

a    a    a    a    a    

a    a    a    a    a    

a    a-y  a    a    a+x    


从而维护二维前缀和


// 注:该代码仅通过暴力对拍证明正确
ll tre[3010][3010],num[3010][3010];
ll n,m,k;
IL void add(ll z,ll x,ll y)
{
    for(R ll i=x;i<=n;i+=(i&-i))
	 for(R ll j=y;j<=m;j+=(j&-j))
	  tre[i][j]+=z;
} 
IL ll qury(ll x,ll y)
{
	ll sum=0;
	for(R ll i=x;i;i-=(i&-i))
	 for(R ll j=y;j;j-=(j&-j))
	  sum+=tre[i][j];
    return sum;	  
}
int main()
{
	read(n);read(m);
	for(R ll i=1;i<=n;++i)
	 for(R ll j=1;j<=m;++j)
	  read(num[i][j]);
	for(R ll i=1;i<=n;++i)
	 for(R ll j=1;j<=m;++j)
	  add(num[i][j]-(num[i-1][j]+num[i][j-1]-num[i-1][j-1]),i,j);  
	read(k);
	while(k--)
	{
		ll key,ax,ay,bx,by,z;
		read(key);
		if(key==1)
		{
	        read(ax);read(ay);read(bx);read(by);read(z);
	        add(z,ax,ay);
	        add(-z,bx+1,ay);
	        add(-z,ax,by+1);
	        add(z,bx+1,by+1);
		}
		else 
		{
			read(ax);read(ay);
			printf("%lld
",qury(ax,ay));
			
		}
	} 
	return 0;
}

③.区域修改区域查询

又是一道模板提

这里便是差分前缀和的极限了

( sum_{i=1}^{x}sum_{j=1}^{y}a[i][j])

(=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}(x-i+1) * (y-j+1) * d[i][j])

(=(x+1) * (y+1)sum_{i=1}^{x}sum_{j=1}^{y}d[i][j] - (x+1) * sum_{i=1}^{x}sum_{j=1}^{y}d[i][j] * j)

( - (y+1) * sum_{i=1}^{x}sum_{j=1}^{y}d[i][j] * i + sum_{i=1}^{x}sum_{j=1}^{y}d[i][j] * i * j)

接下来就又是差分了

维护四个差分数组

(d[i][j],d[i][j]* i,d[i][j]* j,d[i][j]* i* j)

ll tre[4][3055][3055];
ll n,m;
IL void add(ll xx,ll yy,ll k)
{
    if(xx<1||xx>n||yy<1||yy>m) return;
    for(R ll i=xx;i<=n;i+=(i&-i))
     for(R ll j=yy;j<=m;j+=(j&-j))
     {
      tre[0][i][j]+=k;
      tre[1][i][j]+=k*xx;
      tre[2][i][j]+=k*yy;
      tre[3][i][j]+=k*xx*yy;
     } 
}
IL ll qury(ll x,ll y)
{
    ll sum=0;
    for(R ll i=x;i;i-=(i&-i))
     for(R ll j=y;j;j-=(j&-j))
     {
       sum+=(x+1)*(y+1)*tre[0][i][j]-(y+1)*tre[1][i][j]-(x+1)*tre[2][i][j]+tre[3][i][j];
     }
    return sum; 
}
int main()
{
    getchar;read(n);read(m);char key;
//    printf("scanf is %lld and %lld
",n,m);
    while(key=readc(),key!=EOF)
    {
        ll xi,yi,xj,yj,k;
        if(key=='L') 
        {
            read(xi);read(yi);read(xj);read(yj);read(k);
            add(xi,yi,k);
            add(xj+1,yi,-k);
            add(xi,yj+1,-k);
            add(xj+1,yj+1,k);
        }
        else 
        {
            read(xi);read(yi);read(xj);read(yj);
            printf("%d
",qury(xj,yj)-qury(xi-1,yj)-qury(xj,yi-1)+qury(xi-1,yi-1));
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/LovToLZX/p/14335379.html