• 学习笔记 树状数组


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

    写在之前

    树状数组是一个好文明

    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\ =p\sum_{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\n",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\n",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",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\n",qury(xj,yj)-qury(xi-1,yj)-qury(xj,yi-1)+qury(xi-1,yi-1));
            }
        }
        return 0;
    }
    
  • 相关阅读:
    Java通过反射加载的类,变量无法注入
    jmeter http并发测试时报错
    spring 自定义注解
    cmd 和powershell 用git 显示乱码
    centos6.8上安装部署 jhipster-registry
    tcpdf最新版 6.2版
    微信Oauth2.0鉴权 40029 问题
    微信支付学习记录1
    win10 localhost 解析为 ipv6地址 ::1 的解决办法
    PHPExcel 导出时乱码
  • 原文地址:https://www.cnblogs.com/tcswuzb/p/14335379.html
Copyright © 2020-2023  润新知