zoukankan      html  css  js  c++  java
  • 二维树状数组学习笔记

    1 首先从一维的开始,考虑最基础的单点修改,区间查询。树状数组模板1:这个是最基础的,就是定义的应用。

    2再考虑区间修改,单点查询。树状数组模板2

    我们可以想到,在对一段区间(;[l,r];)进行修改时,可以将(;[1,r];)加上(;k),将(;[1,l-1];)减去(;k;)。但这样修改,对于单点查询来说,似乎不是那么的方便,我们可能需要求出(;[1,x];)(;[1,x-1];)的前缀和,再做个差。好像...有点麻烦。

    那么有什么更好的方法呢?——考虑差分

    我们定义(;d_i=a_i-a_{i-1},;a_0=0;)。那么每次修改,就只需要将(;d_l;)(;d_{r+1};)进行修改,而单点查询即为(;sumlimits_{i=1}^xd_i)。直接进行前缀和查询即可。

    3还有的就是区间修改,区间求和了。线段树模板1

    我们跟着上面区间修改,单点查询的思路来,还是考虑差分。

    修改的话,如同上面一样,只对差分数组(;d_{l-1};)(;d_r;)进行修改,但有些许变动。

    我们来看一个式子:

    [sumlimits_{i=1}^rsumlimits_{j=1}^id_j-sumlimits_{i=1}^{l-1}sumlimits_{j=1}^id_j ]

    这个式子前一半表示的是区间(;[1,r];)的和,后一半是区间(;[1,l-1];)的和,二者做差即为所求的区间(;[l,r];)的和。

    尝试对其化简。

    我们先只考虑前一半,即(;sumlimits_{i=1}^rsumlimits_{j=1}^id_j)

    发现(;d_1;)出现了(;r;)次,(;d_2;)出现了(;r-1;)(;ldots) (d_x;)出现了(;r-x+1;)次。至此,我们发现式子可以变为:

    [sumlimits_{i=1}^rd_i imes (r-i+1) ]

    展开可得:

    [(r+1) imes sumlimits_{i=1}^rd_i-sumlimits_{i=1}^rd_i imes i ]

    最终我们只需开两个树状数组来维护(;d_i;)(;d_i imes i;)即可。

    修改:

    il void add(int x,ll val)
    {
    	for(int i=x;i<=n;i+=lowbit(i))
    		c1[i]+=val,c2[i]+=x*val;
    }
    il void real_add(int l,int r,int val)
    {
    	add(l,val);
    	add(r+1,-val);
    }
    

    查询:

    il ll ask(int x)
    {
      ll ans=0;
      for(int i=x;i;i-=lowbit(i))
      	ans+=c1[i]*(x+1)-c2[i];
      return ans;
    }
    il ll real_ask(int l,int r)
    {
      return ask(r)-ask(l-1);
    }
    

    4接下来考虑单点修改,区间查询。二维树状数组模板

    我们只需将一维扩展到二维,代码只需进行一定的修改即可:

    修改:

    inline void add(int x,int y,int val)
    {
    	for(int i=x;i<=n;i+=lowbit(i))
    		for(int j=y;j<=m;j+=lowbit(j))
    			a[i][j]+=val;
    }
    

    查询:

    inline int ask(int x,int y)
    {
    	int ans=0;
    	for(int i=x;i;i-=lowbit(i))
    		for(int j=y;j;j-=lowbit(j))
    			asn+=a[i][j];
    	return ans;
    }
    

    5最后是区间修改,区间查询。上帝造题的七分钟

    回想一下,我们是怎么进行一维的区间修改的——差分。差分数组(;sumlimits_{i=1}^nd_i=a_i;)。再想一下差分数组是如何求出来的。

    我们来看两个式子:

    [sum_i=sum_{i-1}+a_i ]

    [d_i=a_i-a_{i-1} ]

    应该能发现什么吧——差分数组的计算有点像前缀和计算的逆运算。所以,我们可以仿照二维数组的前缀和公式来推出二维数组的差分公式。

    [sum_{ij}=sum_{{i-1}j}+sum_{i{j-1}}-sum_{{i-1}{j-1}} ]

    [d_{ij}=a_{ij}-a_{{i-1}j}-a_{i{j-1}}+a_{{i-1}{j-1}} ]

    在回想一下,我们在进行一维数组的差分修改时是如何修改的——再将其扩展到二维。

    当我们要修改一个二维区间 (;(x_1,y_1) (x_2,y_2);) 所组成的矩形时,只需修改(;(x_1,y_1) (x_1,y_2+1) (x_2+1,y_1) (x_2+1,y_2+1);)四个点。那么,如何修改呢?

    我们来看一个式子:

    [sumlimits_{i=1}^xsumlimits_{j=1}^ysumlimits_{p=1}^isumlimits_{q=1}^jd_{pq} ]

    我们发现,(;d_{11};) 出现了(;x imes y;)次,(;d_{12};)出现了 (;x imes {(y-1)};)(;ldots) (;d_{pq};)出现了(;{(x-p+1)} imes {(y-q+1)};)次。

    我们将式子展开,可得到

    [sumlimits_{i=1}^xsumlimits_{j=1}^y{d_{ij} imes (x-i+1) imes (y-j+1)} ]

    再次展开

    [{(x+1) imes (y+1) imes sumlimits_{i=1}^xsumlimits_{j=1}^yd{ij}}-{(y+1) imes sumlimits_{i=1}^xsumlimits_{j=1}^y{d{ij} imes i}}-{(x+1) imes sumlimits_{i=1}^xsumlimits_{j=1}^y{d{ij} imes j}}+{sumlimits_{i=1}^xsumlimits_{j=1}^y{d{ij} imes i imes j}} ]

    那么,我们就只需开四个树状数组来分别维护(d_{ij}quad d_{ij} imes iquad d{ij} imes jquad d{ij} imes i imes j)即可。

    区间查询就是(;sum_{{x_2}{y_2}}-sum_{{x_2}{y_1-1}}-sum_{{x_1-1}{y_2}}+sum_{{x_1-1}{y_1-1}})

    完整代码:

    //2020.11.4
    //二维树状数组,区间修改,区间查询
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2050;
    #define NEKO puts("NEKO")
    #define il inline
    #define vocaloid(v) (v>='0'&&v<='9')
    il int read()
    {
       int x=0,flag=1;char v=getchar();
       while(!vocaloid(v)) {if(v=='-') flag=-1;v=getchar();}
       while(vocaloid(v)) {x=(x<<1)+(x<<3)+v-'0';v=getchar();}
       return x*flag;
    }
    il int lowbit(int x) {return x&(-x);}
    int n,m,c1[maxn][maxn],c2[maxn][maxn],c3[maxn][maxn],c4[maxn][maxn];
    char ch;int X1,X2,Y1,Y2,val;
    il void add(int x,int y,int val)
    {
       for(int i=x;i<=n;i+=lowbit(i))
       	for(int j=y;j<=m;j+=lowbit(j))
       	{
       		c1[i][j]+=val;
       		c2[i][j]+=val*x;
       		c3[i][j]+=val*y;
       		c4[i][j]+=val*x*y;
       	} 
    }
    il void real_add(int X1,int Y1,int X2,int Y2,int val)
    {
       add(X1,Y1,val);
       add(X1,Y2+1,-val);
       add(X2+1,Y1,-val);
       add(X2+1,Y2+1,val);
    }
    il int ask(int x,int y)
    {	
       int ans=0;
       for(int i=x;i>0;i-=lowbit(i))
       	for(int j=y;j>0;j-=lowbit(j))
       		ans+=(x+1)*(y+1)*c1[i][j]-(y+1)*c2[i][j]-(x+1)*c3[i][j]+c4[i][j];
       return ans;
    }
    il int real_ask(int X1,int Y1,int X2,int Y2)
    {
       return ask(X2,Y2)-ask(X2,Y1-1)-ask(X1-1,Y2)+ask(X1-1,Y1-1);
    }
    int main()
    {
       cin>>ch;n=read(),m=read();
       while(cin>>ch)
       {
       	if(ch=='L')
       	{
       		X1=read(),Y1=read(),X2=read(),Y2=read(),val=read();
       		real_add(X1,Y1,X2,Y2,val);
       	}
       	if(ch=='k')
       	{
       		X1=read(),Y1=read(),X2=read(),Y2=read();
       		printf("%d
    ",real_ask(X1,Y1,X2,Y2));
       	}
       }
       return 0;
    }
    

    (PS) 部分内容参考自以下几篇博客:

    Lv1_kangdi——二维树状数组总结及模板

    fmj_123——luogu-P4514-二维树状数组

    十分感谢他们的博客对我学习此内容的帮助。

  • 相关阅读:
    hdu 4710 Balls Rearrangement()
    hdu 4707 Pet(DFS水过)
    hdu 4706 Children's Day(模拟)
    hdu 4712 Hamming Distance(随机函数暴力)
    csu 1305 Substring (后缀数组)
    csu 1306 Manor(优先队列)
    csu 1312 榜单(模拟题)
    csu 1303 Decimal (数论题)
    网络爬虫
    Python处理微信利器——itchat
  • 原文地址:https://www.cnblogs.com/MIKU5201314/p/14086661.html
Copyright © 2011-2022 走看看