返回顶部

树状数组板子

定义

  • 树状数组是一个查询和修改复杂度都为log(n)的数据结构。主要用于查
    询任意区间的连续元素和,但是每次只能修改一个元素的值;即树状数
    组支持的操作:单点修改,区间查询(当区间长度为1时,即单点查询)
  • 树状数组逻辑上是一棵树,但实际上只是一个数组。

image

  • 设C的某个元素下标为x,则这个结点(前缀和)的管辖区间是2^k个元素(其
    中k为x的二进制数的末尾0的个数),且该区间的最后一个元素为Ax
    Cx的双亲结点下标y就等于x的二进制数在最后一个1的位置上加1后得到的值。

    单点修改 区间查询

    点击查看代码
    #include <bits/stdc++.h>
    using namespace std;
    #define int long long 
    int n,A[100005],C[100005];
    int lowbit(int x)
    {
    	return (x&(-x));
    }
    int getsum(int x)
    {
    	int s=0;
    	while(x)
    	{
    		s+=C[x];
    		x-=lowbit(x);
    	}
    	return s;
    }
    void add(int n,int x,int key)
    {
    	while(x<=n)
    	{
    		C[x]+=key;
    		x+=lowbit(x);
    	}
    }
    signed main()
    {
    	cin>>n;
    	for(int i=1;i<=n;i++)
    	{
    		cin>>A[i];
    		add(n,i,A[i]);
    	}
    	int t;
    	cin>>t;
    	string s;
    	int k,p;
    	for(int i=1;i<=t;i++)
    	{
    		cin>>s>>k>>p;
    		if(s=="ADD")
    		{
    			add(n,k,p);
    		}else
    		{
    			printf("%lld\n",getsum(p)-getsum(k-1));
    		}
    	}
    	return 0;
    }
    

    区间修改 单点查询

    点击查看代码
    #include <bits/stdc++.h>
    using namespace std;
    #define int long long 
    int n,A[100005],C[100005];
    inline int lowbit(int x)
    {
    	return (x&(-x));
    }
    int getsum(int x)
    {
    	int s=0;
    	while(x)
    	{
    		s+=C[x];
    		x-=lowbit(x);
    	}
    	return s;
    }
    void add(int n,int x,int key)
    {
    	while(x<=n)
    	{
    		C[x]+=key;
    		x+=lowbit(x);
    	}
    }
    signed main()
    {
    	scanf("%lld",&n);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%lld",&A[i]);
    		add(n,i,A[i]-A[i-1]);
    	}
    	int m;
    	scanf("%lld",&m);
    	string s;
    	for(int i=1;i<=m;i++)
    	{
    		cin>>s;
    		int a,b,c;
    		if(s=="QUERY")
    		{
    			scanf("%lld",&a);
    //			cout<<"#";
    			printf("%lld\n",getsum(a));
    		}else if(s=="ADD")
    		{
    			scanf("%lld%lld%lld",&a,&b,&c);
    //			for(int j=a;j<=b;j++)
    			add(n,a,c);
    			add(n,b+1,-c);
    		}
    	}
    	return 0;
    }
    

    区间修改 区间查询

    点击查看代码
    #include <bits/stdc++.h>
    using namespace std;
    #define int long long 
    int n,A[100005],C[100005],C2[100005];
    inline int lowbit(int x)
    {
    	return (x&(-x));
    }
    int getsum(int x)
    {
    	int i=x;
    	int s=0;
    	while(x)
    	{
    		s+=(i+1)*C[x]-C2[x];
    		x-=lowbit(x);
    	}
    	return s;
    }
    void add(int n,int x,int key)
    {
    	int i=x;
    	while(x<=n)
    	{
    		C[x]+=key;
    		C2[x]+=(long long)i*key;
    		x+=lowbit(x);
    	}
    }
    signed main()
    {
    //	if(LONG_LONG_MAX>4611686018427387904)cout<<1;
    	scanf("%lld",&n);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%lld",&A[i]);
    		add(n,i,A[i]-A[i-1]);
    	}
    	int m;
    	scanf("%lld",&m);
    	string s;
    	for(int i=1;i<=m;i++)
    	{
    		cin>>s;
    		int a,b,c;
    		if(s=="SUM")
    		{
    			scanf("%lld%lld",&a,&b);
    //			cout<<"#";
    //			int ans=0;
    //			for(int i=a;i<=b;i++)ans+=getsum(i);
    //			printf("%lld\n",ans);
    			printf("%lld\n",getsum(b)-getsum(a-1));
    		}else if(s=="ADD")
    		{
    			scanf("%lld%lld%lld",&a,&b,&c);
    //			for(int j=a;j<=b;j++)            
    			add(n,a,c);
    			add(n,b+1,-c);
    		}
    	}
    	return 0;
    }
    
  • 区间查询即区间内单点查询结果的和
    仍然沿用c数组,考虑A数组[1,x]区间和的计算。b[1]被累加了x次,b[2]被
    累加了x-1次,...,b[x]被累加了1次。因此得到
    ∑a[i]=∑{b[i](x-i+1)}=∑{b[i](x+1) - b[i]i}=(x+1)∑b[i]-∑(b[i]i)
    所以我们再用树状数组维护一个数组b2[i]=b[i]
    i,即可完成任务。
    用树状数组C1维护b[i]的前缀和,C2维护b2[i]的前缀和。

  • 另一种思路:
    首先,看更新操作Update(s,t,d)把区间A[s]...A[t]都增加d,我们沿用数组b[i],表示
    的意义修改一下,即表示A[i]...A[n]的共同增量,n是数组的大小。那么update操作可
    以转化为:
    1)令b[s]=b[s]+d,表示将A[s]...A[n]同时增加d,但这样A[t+1]...A[n]就多加了d
    2)再令b[t+1]=b[t+1]-d,表示将A[t+1]...A[n]同时减d;
    然后来看查询操作Ask(s,t),求A[s]...A[t]的区间和,转化为求前缀和,设
    sum[i]= A[1]+...+A[i],则A[s]+...+A[t]=sum[t]-sum[s-1],那么前缀和sum[x]又如何
    求呢?它由两部分组成,一是数组的原始和,二是该区间内的累计增量和, 把数组A的
    原始值保存在数组org中,并且b[i]对sum[x]的贡献值为b[i](x+1-i),那么
    sum[x]=org[1]+...+org[x]+b[1]
    x+b[2](x-1)+b[3](x-2)+...+b[x]1
    =org[1]+...+org[x]+segma(b[i]
    (x+1-i))
    =segma(org[i])+(x+1)segma(b[i])-segma(b[i]i),1<=i<=x
    这其实就是三个数组org[i], b[i]和b[i]i的前缀和,org[i]的前缀和保持不变,事先
    就可以求出来,b[i]和b[i]
    i的前缀和是不断变化的,可以用两个树状数组来维护。

    二维树状数组

    修改

    点击查看代码
    
    void add(int x, int y, int key) {
    for(int i = x; i <= n; i += lowbit(i))
    {
    	for(int j = y; j <= n; j += lowbit(j)) 
    	{
    		 C[i][j] += key;
    	}
    }
    
    

    查询

    点击查看代码
    int getsum(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+=g[i][j];
    		}	
    	}	
    	return ans;
    }
    
  • 设二维数组为:
    a[][]={ {a11,a12,a13,a14,a15,a16,a17,a18},
    {a21,a22,a23,a24,a25,a26,a27,a28},
    {a31,a32,a33,a34,a35,a36,a37,a38},
    {a41,a42,a43,a44,a45,a46,a47,a48}};
    那么C[1][1] = a11,C[1][2] = a11 + a12;
    如此当C[1][i]...C[1][j]时跟一维的树状数组是没有什么区别的
    那么C[2][1] = a11 + a21,C[2][2] = a11 + a12 + a21 + a21,
    如此可以发现
    其实C[2][i].....C[2][j],就是C[1][],C[2][],单独的两个一维树
    状数组同一位置的值合并在一起
    而C[3][1] = a31,C[3][2] = a31 + a32 ......
    而C[4][1] = a11 + a21 + a31 + a41,C[4][2] = a11 + a12 +
    a21 + a22 + a31 + a32 + a41 + a42
    所以二维数组的规律就是,不管是横坐标还是纵坐标,将他们单独
    拿出来,他们都符合x += lowbit(x),属于它的父亲节点.

posted @ 2024-02-19 18:35  wlesq  阅读(24)  评论(0编辑  收藏  举报