认识树状数组、树状数组的简单使用(原数组的单点修改和区间查询)

一、树状数组

<1>、普通数组
以普通数组为例:

单点修改 时间复杂度为O(1),分段查询 时间复杂度为O(n)。

以普通数组的前缀和为例:

分段查询 时间复杂度为O(1),单点修改 时间复杂度为O(n)。(改一个值影响后面的前缀和)。

<2>、树状数组
1、原理

树状数组图片

如图,每个树状数组管理着 2的k次方个数字(此数字的二进制表达的末尾0的个数k)

例如:

d[6]=a[6]+a[5]
110    2^1
d[8]=a[1]+...+a[8]
1000 2^3
2、询问
若要询问14的前缀和:
d[14]+d[12]+d[8];
同理询问11的前缀和:
d[11]+d[10]+d[8];
保证了查询复杂度为O(logn)
若查询13的前缀和
1101=13;有3个1
1101 d[13] 管辖2^0
1100 d[12] 管辖2^2
1000 d[8]  管辖2^3
           管辖总数为13

则每次抹掉末尾1的个数

由上得树状数组单词查询复杂度为O(logn)

3、修改
若要修改6的值
则d[16]、d[8]、d[6]都需要修改//详情见上图
则将末尾1处加1

110=6

11+1=100再加上原本末尾的0
1000=8

同理
1+1=10再加000
10000=16
再例:
1101=13
1110=14
10000=16
4、lowbit

由上述可知,增加和删除 (末尾1及后面的0的十进制数),故此用到lowbit方法

lowbit(int x){
	return x&(-x);
}

/*
1100 = 12
补码0011+1=0100

 1100
&
 0100
=
 0100
*/

10100
01011+1=01100
只找到末尾1,取反将后面的0变为1,再加一,而末尾1之前的数字&操作之后则无关
10100  ->  01011    ->      01100
5、实现
int d[100005];
int lowbit(int x){
	return x&(-x);
}

int query(int x){//查询
	int res=0;
	while(x){
		res+=d[x];
		x-=lowbit(x);
	}
	return res;
}

void update(int x,int v){//修改,在a[x]加v
	while(x<=n){
		d[x]+=v;
		x+=lowbit(x);
	}
}
6、拓展

区间修改/单点查询:

代码不变

若要在3-6区间每个加5
只需在3处+5,在7处-5即可,此时query变成单点查询。

即可完成区间修改和单点查询

区间最值:更推荐线段树

<3>、二维树状数组
解决:单点修改+sum[x][y]      
      //从1到x行中每一行前y个数字的和。
解决:子矩阵的和
解决:几个子矩阵的差或和。
int d[301][301];
void update(int x,const int &y,const int &v){
	for(;x<=n;x+=(x&(-x)))
		for(int j=y;j<=n;j+=(j&(-j)))
			d[x][j]+=v;
}
int getsum(int x,const int &y){
	int res=0;
	for(;x;x-=(x&(-x)))
		for(int j=y;j;j-=(j&(-j)))
			res+=d[x][j];
	return res;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值