树状数组

在信息学竞赛中,我们经常需要动态维护一个数列的前缀和信息;这当然可以用 线段树分块 来实现,但我们还有更轻量级的数据结构 -----> 树状数组。
与线段树类似,树状数组也是用于动态查询区间(其实是“前缀”)信息、支持修改的数据结构。
与线段树相比,它更简洁,实现起来也更方便,但是它能维护的信息比较有限。
树状数组 ( B i n a r y I n d e x e d T r e e ( B . I . T ) , F e n w i c k T r e e ) (Binary Indexed Tree(B.I.T), FenwickTree) (BinaryIndexedTree(B.I.T),FenwickTree)是一个查询和修改复杂度都为log(n)的数据结构。
可以实现单点(区间)修改和区间查询

算法思想

那我们如何实现这样一个数据结构呢?

  • 任何一个十进制数都可以转化为一个二进制数,这个二进制数的位数是 l o g log log级别的。
  • 类似的,我们在求前缀 「 1 , a 」 「1,a」 1a的信息时,我们也希望将其分解成若干个不相交的子集的和。

于是,如下图,一个存放子集的和的树结构就诞生了。
简图
该结构满足以下性质

  1. 每个节点 c [ x ] c[x] c[x]保存以它为根的子树中所有叶节点的和。
  2. c[x]的子节点个数 = = l o w b i t ( x ) ==lowbit(x) ==lowbit(x)的位数
  3. x的父亲节点是 x + l o w b i t ( x ) x+lowbit(x) x+lowbit(x)

lowbit(x)表示取出x二进制最低位的1及其之后的0表示的数
图中 c c c数组方框里的数表示其二进制编码
线表示该子集由以下几个子集得来
例如: c [ 2 ] = c [ 1 ] + a [ 2 ] , c [ 4 ] = c [ 2 ] + c [ 3 ] + a [ 4 ] c[2]=c[1]+a[2], c[4]=c[2]+c[3]+a[4] c[2]=c[1]+a[2],c[4]=c[2]+c[3]+a[4]
省略a数组不看,我们发现x的父亲节点的下标是x加上其二进制最后一位的1
(即x+lowbit(x)
那么已经可以解决单点修改的问题了
假如将x位置上的数加上y
那么我们只需要将x,x的父节点,x的父节点的父节点 … 都加上y就可以了
时间复杂度是log级别的
而不用修改其他节点的值,因为其他节点的值不由x得来;
表示成代码

inline void update(int x,int y)
{
	for(;x<=n;x+=lowbit(x)) c[x]+=y;
}

那如何查询前缀和呢?
先看几个例子

  • s [ 6 ] = c [ 6 ] + c [ 4 ] − > ( 二 进 制 表 示 ) s [ 110 ] = c [ 110 ] + c [ 100 ] s[6]=c[6]+c[4]-> ( 二进制表示 ) s[110]=c[110]+c[100] s[6]=c[6]+c[4]>s[110]=c[110]+c[100]
  • s [ 5 ] = c [ 5 ] + c [ 4 ] − > s [ 101 ] = c [ 101 ] + c [ 100 ] s[5]=c[5]+c[4] -> s[101]=c[101]+c[100] s[5]=c[5]+c[4]>s[101]=c[101]+c[100]
  • s [ 13 ] = c [ 13 ] + c [ 12 ] + c [ 8 ] − > s [ 1101 ] = c [ 1101 ] + c [ 1100 ] + c [ 1000 ] s[13]=c[13]+c[12]+c[8] -> s[1101]=c[1101]+c[1100]+c[1000] s[13]=c[13]+c[12]+c[8]>s[1101]=c[1101]+c[1100]+c[1000]

可以看出每次求前缀和[1,x],从x开始,每次减去下标二进制最后一个1,然后加上 c [ x ] c[x] c[x],到0为止即可;
与修改操作相反,每次减去lowbit(x)

inline int sum(int x)
{
	int res=0;
	for(;x;x-=lowbit(x)) res+=c[x];
	return res;
}

树状数组存储的是前缀和信息,对于前缀信息能否转化成区间信息要加以注意。
以下是代码

1.单点修改+区间查询

1.lowbit
inline int lowbit(int n) 
{
	return n&(-n);
} 
2.单点加法
inline void update(int x,int y)
{
	for(;x<=n;x+=lowbit(x)) c[x]+=y;
}
3.查询前缀和
inline int sum(int x)
{
	int res=0;
	for(;x;x-=lowbit(x)) res+=c[x];
	return res;
}

完整代码

#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
const int N=1000010;
int c[N];
inline int lowbit(int n) 
{
	return n&(-n);
} 
inline void update(int x,int y)
{
	for(;x<=n;x+=lowbit(x)) c[x]+=y;
}
inline int sum(int x)
{
	int res=0;
	for(;x;x-=lowbit(x)) res+=c[x];
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	int opt,x,k;
	int a,b;
	for(int i=1;i<=n;i++) {
		scanf("%d",&x);
		update(i,x);
	}
	for(int i=1;i<=m;i++) {
		scanf("%d%d%d",&opt,&a,&b);
		if(opt) update(a,b);
		else printf("%d\n",sum(b)-sum(a-1));
	}
	return 0;
}

2.区间修改+区间查询

首先需要两棵树状数组
用结构体存储

#define LL long long
typedef  LL treetype;
struct Bit
{
	treetype c[N];
	inline int lowbit(int _n) 
	{ 
		return _n&(-_n); 
	}
	inline void update(int x,treetype y)
	{
		for(;x<=n;x+=lowbit(x)) 
			c[x]+=y;
		return;
	}
	inline treetype sum(int x)
	{
		treetype res=0;
		for(;x>=1;x-=lowbit(x)) 
			res+=c[x];
		return res;
	}
};
Bit c1,c2;

利用差分的思想
先将整个数列差分存入c数组
那么 a [ i ] = c [ 1 ] + c [ 2 ] + . . . + c [ i ] a[i]=c[1]+c[2]+...+c[i] a[i]=c[1]+c[2]+...+c[i]; 即 a [ i ] = ∑ j = 1 i c [ j ] a[i]=\sum_{j=1}^{i}c[j] a[i]=j=1ic[j] -------(1)
前缀和 s u m [ i ] = ∑ j = 1 i a [ j ] sum[i]=\sum_{j=1}^{i}a[j] sum[i]=j=1ia[j]
s u m [ i ] = a [ 1 ] + a [ 2 ] + . . . + a [ i ] sum[i]=a[1]+a[2]+...+a[i] sum[i]=a[1]+a[2]+...+a[i] --------(2)
我们将1式代入2式,得到
s u m [ i ] = c [ 1 ] + ( c [ 1 ] + c [ 2 ] ) + . . . + ( c [ 1 ] + c [ 2 ] + . . . + c [ i ] ) sum[i]=c[1]+(c[1]+c[2])+...+(c[1]+c[2]+...+c[i]) sum[i]=c[1]+(c[1]+c[2])+...+(c[1]+c[2]+...+c[i])
= i ∗ c [ 1 ] + ( i − 1 ) ∗ c [ 2 ] + ( i − 2 ) ∗ c [ 3 ] + . . . + 1 ∗ c [ i ] =i * c[1]+(i-1)*c[2]+(i-2)*c[3]+...+1 * c[i] =ic[1]+(i1)c[2]+(i2)c[3]+...+1c[i]
= i ∗ ( c [ 1 ] + c [ 2 ] + c [ 3 ] + . . . + c [ i ] ) − ( 0 ∗ c [ 1 ] + 1 ∗ c [ 2 ] + . . . + ( i − 1 ) ∗ c [ i ] ) =i * (c[1]+c[2]+c[3]+...+c[i]) - (0*c[1]+1 * c[2]+...+(i-1)*c[i]) =i(c[1]+c[2]+c[3]+...+c[i])(0c[1]+1c[2]+...+(i1)c[i])
s u m [ i ] = i ∗ ∑ j = 1 i c [ j ] − ∑ j = 1 i ( j − 1 ) ∗ c [ j ] sum[i]=i*\sum_{j=1}^{i}c[j]-\sum_{j=1}^{i}(j-1)*c[j] sum[i]=ij=1ic[j]j=1i(j1)c[j]
所以用一个树状数组存 c [ 1 ] − − c [ n ] c[1]--c[n] c[1]c[n]
另一个树状数组存 ( i − 1 ) ∗ c [ i ] (i-1)*c[i] (i1)c[i]即可

1.区间加法
inline void modify(int x,int y,treetype val)
{
	c1.update(x,val);
	c1.update(y+1,-val);
	c2.update(x,(x-1)*val);
	c2.update(y+1,-y*val);
	return;
}
2.前缀和查询
inline treetype prefix(int x)
{
	return x*c1.sum(x)-c2.sum(x);
}

完整代码

#include<iostream>
#include<cstdio>
#define LL long long
using namespace std;
typedef  LL treetype;
const int N=1e5+5;
int n,m;
struct Sgtree
{
	struct Bit
	{
		treetype c[N];
		inline int lowbit(int _n) 
		{ 
			return _n&(-_n); 
		}
		inline void update(int x,treetype y)
		{
			for(;x<=n;x+=lowbit(x)) 
				c[x]+=y;
			return;
		}
		inline treetype sum(int x)
		{
			treetype res=0;
			for(;x>=1;x-=lowbit(x)) 
				res+=c[x];
			return res;
		}
	};
	Bit c1,c2;
	inline void modify(int x,int y,treetype val)
	{
		c1.update(x,val);
		c1.update(y+1,-val);
		c2.update(x,(x-1)*val);
		c2.update(y+1,-y*val);
		return;
	}
	inline treetype prefix(int x)
	{
		return x*c1.sum(x)-c2.sum(x);
	}
};
Sgtree t;
int main()
{
	scanf("%d%d",&n,&m);
	LL opt,x,y,k;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&x);
		t.modify(i,i,x);
	}
	while(m--) 
	{
		scanf("%lld%lld%lld",&opt,&x,&y);
		if(opt==1) 
		{
			scanf("%lld",&k);
			t.modify(x,y,k);
		}
		else
			printf("%lld\n",t.prefix(y)-t.prefix(x-1));
	}
	return 0;
}

在这里插入图片描述

3.二维树状数组

如同一维的树状数组,把二维的树状数组定义如下
s u m [ i ] [ j ] = ∑ i = x − c ( x ) + 1 x ∑ j = y − c ( y ) + 1 y a [ i ] [ j ] sum[i][j]=\sum_{i=x-c(x)+1}^{x}\sum_{j=y-c(y)+1}^{y}a[i][j] sum[i][j]=i=xc(x)+1xj=yc(y)+1ya[i][j]
其实加一层循环就可以了

#include<iostream>
#include<cstdio>
#define LL long long
using namespace std;
const int N=5000+5;
int c[N][N];
int n,m;
typedef LL treetype;
inline int lowbit(int n)
{
	return n&(-n);
}
inline void update(int x,int y,treetype key)
{
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=m;j+=lowbit(j))
			c[i][j]+=key;
	return;
}
inline treetype sum(int x,int y)
{
	treetype res=0;
	for(int i=x;i;i-=lowbit(i))
		for(int j=y;j;j-=lowbit(j)) 
			res+=c[i][j];
	return res;
}
int main()
{
	return 0;
}

推荐两位大佬的博客

友情链接

  1. 机房大佬hoedx的博客
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值