【模板】树状数组(7.26)

update:二维树状数组的实现

一.概述

树状数组,是一个区间查询单点修改复杂度都为log(n)的数据结构。主要用于查询任意两点之间的所有元素之和。

树状数组的题多半要转化,利用差分数组,可以将区间修改变为单点修改单点查询变为区间查询(即前缀和)。只要满足单点修改,我们总能用树状数组来维护前缀和。

b i t [ i ] bit[i] bit[i]数组可以看作一个树形结构,它满足以下三个性质:

  1. 每个内部节点 b i t [ x ] bit[x] bit[x]保存以它为根的子树中所有叶节点的和
  2. 每个内部节点 b i t [ x ] bit[x] bit[x]的子节点个数等于 l o w b i t ( x ) lowbit(x) lowbit(x)的位数
  3. 除树根外,每个内部节点bit[x]的父节点是bit[x+lowbit(x)]
  4. 树的深度为 O ( l o g N ) O(logN) OlogN
  5. b i t [ x ] bit[x] bit[x]表示区间 [ x − l o w b i t ( x ) + 1 , x ] [x-lowbit(x)+1,x] [xlowbit(x)+1,x]

个人认为它比较数学化,利用了数的性质(尤其是二进制)。

二.例题

  1. 模拟类
  2. 计数类(偏数学)

一维树状数组:

1.单点修改,区间查询

2.区间修改,区间查询
引入差分概念:

c [ i ] = a [ i ] − a [ i − 1 ] c[i]=a[i]-a[i-1] c[i]=a[i]a[i1]

那么某个元素的值其实等于 a [ i ] = ∑ i = 1 n c i a[i]=\sum_{i=1}^{n}c_{i} a[i]=i=1nci
那么区间和呢?

我们可以知道

s u m [ x ] = ∑ i = 1 x ∑ j = 1 i c [ i ] = ∑ i = 1 x c [ i ] ∗ ( x − i + 1 ) = ∑ i = 1 x c [ i ] ∗ ( x + 1 ) − c [ i ] ∗ i sum[x]=\sum_{i=1}^{x}\sum_{j=1}^{i}c[i]=\sum_{i=1}^{x}c[i]*(x-i+1)=\sum_{i=1}^{x}c[i]*(x+1)-c[i]*i sum[x]=i=1xj=1ic[i]=i=1xc[i](xi+1)=i=1xc[i](x+1)c[i]i

因此我们需要维护两个值:差分数组 c [ i ] c[i] c[i] b [ i ] = c [ i ] ∗ i b[i]=c[i]*i b[i]=c[i]i

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
int n,Q,bit[N],bit2[N],a[N];
void update(int x,int k) {
	for(int i=x;i<=n;i+=i&-i)
	    bit[i]+=k,bit2[i]+=x*k; 
}
int get_sum(int x) {
	int tot=0,tot2=0;
	for(int i=x;i;i-=i&-i) 
	    tot+=bit[i],tot2+=bit2[i];
	return tot*(x+1)-tot2;
}
signed main() {
    n=read(),Q=read();
    for(int i=1;i<=n;i++) {
    	a[i]=read();
    	update(i,a[i]-a[i-1]);
	}
	while(Q--) {
		int type=read(),l=read(),r=read();
		if(type==1) {
			int x=read();
			update(l,x);
			update(r+1,-x);
		}
		else printf("%lld\n",get_sum(r)-get_sum(l-1));
	}
}

二维树状数组:

https://blog.csdn.net/qq_35885746/article/details/89247993

1. 单点修改,区间查询
思路: b i t [ i ] [ j ] bit[i][j] bit[i][j]表示 ( i − l o w b i t ( i ) + 1 , j − l o w b i t ( j ) + 1 ) (i-lowbit(i)+1,j-lowbit(j)+1) (ilowbit(i)+1,jlowbit(j)+1) ( i , j ) (i,j) (i,j)的子矩阵和,sum函数显然很好写:

ll Sum(int x, int y) {
    ll tot = 0;
    for (int i = x; i; i -= i & -i) {
        for (int j = y; j; j -= j & -j) {
            tot += bit[i][j];
        }
    }
    return tot;
}

关键的是update函数。仔细思考: i i i, j j j的父节点分别是 i + l o w b i t ( i ) − 1 i+lowbit(i)-1 i+lowbit(i)1 j + l o w b i t ( j ) − 1 j+lowbit(j)-1 j+lowbit(j)1,我们要更新的 x x x, y y y必然都是 i i i, j j j的祖先节点,所以用二重循环遍历祖先节点即可。

void update(int x, int y, int k) {
    for (int i = x; i <= n; i += i & -i) {
        for (int j = y; j <= m; j += j & -j) {
            bit[i][j] += k;
        }
    }
}

查询操作是 O ( 1 ) O(1) O(1):

long long get() {
    printf("%lld\n", Sum(c,d) - Sum(c,b-1) - Sum(a-1,d) + Sum(a-1,b-1));
}

总时间复杂度是 O ( l o g n ∗ l o g n ∗ t ) O(logn*logn*t) Olognlognt,t是操作总数

2.区间修改,单点查询
思路:这里维护的数组值是差分数组的前缀和。
我们可以令差分数组 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 [ i ] [ j ] = ∑ x = 1 i ∑ y = 1 j d [ x ] [ y ] a[i][j]=\sum_{x=1}^{i}\sum_{y=1}^{j}d[x][y] a[i][j]=x=1iy=1jd[x][y]
至于证明,显然成立(虽然这个式子比较难想)

long long Sum(int x, int y) {
    long long tot = 0;
    for (int i = x; i; i -= i & -i) {
        for (int j = y; j; j -= j & -j) {
            tot += bit[i][j];
        }
    }
    return tot;
}
long long get() {
    printf("%lld\n", Sum(x,y));
}

然后考虑 d [ i ] [ j ] d[i][j] d[i][j] 数组的变化,发现一次区间修改相当于把四个点的d值进行修改。update函数把sum累加,正好就是点的变化。

void update(int x, int y, int k) {
    for (int i = x; i <= n; i += i & -i) {
        for (int j = y; j <= m; j += j & -j) {
            bit[i][j] += k;
        }
    }
}

3.区间修改,区间查询
思路:仍然维护d数组,修改函数同上。
下面讨论如何计算区间的和:
s u m [ x ] [ y ] = ∑ i = 1 x ∑ j = 1 y a [ i ] [ j ] = ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ ( x − i + 1 ) ∗ ( y − j + 1 ) = ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ ( x y + x + y + 1 ) − d [ i ] [ j ] ∗ i ∗ ( y + 1 ) − d [ i ] [ j ] ∗ j ∗ ( x + 1 ) + d [ i ] [ j ] ∗ i ∗ j sum[x][y]=\sum_{i=1}^{x}\sum_{j=1}^{y}a[i][j]=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*(x-i+1)*(y-j+1)=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*(xy+x+y+1)-d[i][j]*i*(y+1)-d[i][j]*j*(x+1)+d[i][j]*i*j sum[x][y]=i=1xj=1ya[i][j]=i=1xj=1yd[i][j](xi+1)(yj+1)=i=1xj=1yd[i][j](xy+x+y+1)d[i][j]i(y+1)d[i][j]j(x+1)+d[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

这样就可以解决上述问题了

void update(int x, int y, long long k) {
    for (int i = x; i <= n; i += i & -i) {
        for (int j = y; j <= m; j += j & -j) {
            bit1[i][j] += k, bit2[i][j] += 1LL * k * x, bit3[i][j] += 1LL * k * y,
                bit4[i][j] += 1LL * x * y * k;
        }
    }
}
long long Sum(int x, int y) {
    long long tot = 0;
    for (int i = x; i; i -= i & -i) {
        for (int j = y; j; j -= j & -j) {
            tot += bit4[i][j] - (x + 1) * bit3[i][j] - (y + 1) * bit2[i][j] + (x + 1) * (y + 1) * bit1[i][j];
        }
    }
    return tot;
}
long long get() {
    printf("%lld\n", Sum(c, d) - Sum(c, b - 1) - Sum(a - 1, d) + Sum(a - 1, b - 1));
}
posted @ 2020-07-26 21:34  仰望星空的蚂蚁  阅读(9)  评论(0)    收藏  举报  来源