饭后粗谈树状数组

今天我们来讨论一种我自认为十分实用的数据结构——树状数组。在我看来树状数组十分易懂,一定程度上与线段树相比有极大的优势。接下来我们来深究树状数组。

前提

你不需要会线段树,但你需要知道线段树的存贮结构(因为我将通过比较线段树来讲解)。

正文

我们先来见识一下线段树的储存方式:


应该很好懂,接下来就是主角登场了:


当当当当……是不是觉得很优美?什么?你居然否认!哼哼,等我讲完你就会觉得优美了。

下面是树状数组的存储方法(敲黑板):以上方的树为例,我们令八个子节点的权值各为a[1],a[2],a[3]....a[8];然后定义c[i]代表子树的叶子结点的权值之和:


有图有真相,相信大家都已经看懂了。

接下来就是树状数组的构建,请大家先拿出草稿纸。我们可以发现,每一个c[i]都在一个节点的左儿子上,所以对照式子可以发现c[i]=a[i-2^k+1]+a[i-2^k+2]+……+a[i](k为i的二进制中从最低位到高位连续零的长度),所以我们需要求出k,至于怎么求,这就是为什么要你们拿草稿纸的原因,大家可以自行推算,这里我先把答案亮出来吧:我们引入lowbit(x)求k,具体操作就是t&(-t),为了方便,不建议大家写一个函数,可以直接定义:#define lowbit(t) t&(-t)  (如果你是P党,那么恕我救不了你)。现在树状数组的核心已经讲述完成了,接下来的就十分简单了,大家可以打开网易云音乐,搜索用户落九思,进入他的歌单,一边听歌一边学树状数组。对于树状数组的构建,我们可以把c全部看成权值为0,那么,构建操作就成了单点修改,add(i,a[i])即可,而单点修改,当c[i]被更改时,我们发现c[i]的祖先上的c也需要更改,然而有一个好消息:下一个需要修改的祖先的c数组的编号为i+lowbit[i](大家可以拿出草稿纸证一下),于是,我们就顺理成章的能写出add了:

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

至于剩下的区间查询,不过是add的反向操作,我此处就不细讲了,留给大家思考。但此处还是给出代码:

int getsum(int x)
{
int ans=0;
for(int i=x;i>0;i-=lowbit(i))
ans+=c[i];
return ans;
}
于是乎,树状数组就讲完了,它的时间复杂度为nlogn,它在一些情况下可以代替线段树,取得更好的效果,但它不支持区间修改,所以慎用,慎用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值