• 二维树状数组


    前置知识:树状数组学习

    二维树状数组简介

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

    设原数组上的点为 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;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    时间复杂度为 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    若要求 ∑ 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
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
  • 相关阅读:
    linux下使用Docker Compose部署Spug实现公网远程访问
    Day4 数据分析 Excel图表【零基础】
    MyBatis中怎样查看执行的sql语句日志?
    Java从零学起(七)----面向对象(中)
    如何在linux系统中设置定时任务?
    Hadoop发展史
    深入理解联邦学习——纵向联邦学习
    web测试——业务测试2
    YOLOv8-Cls推理详解及部署实现
    vue基础知识和原理(二)
  • 原文地址:https://blog.csdn.net/tanjunming2020/article/details/127988678