树状数组

我和你徜徉在一片花海之中,你的笑颜化为一缕清风拂过我的心,莹莹目光饱含深情与温柔。

你指着前面问,“gigo,那是什么?”

我望了望,笑着回到,“那是一片树状数组哇。”

                                                                                                                                                                              

咳咳,进入正题。

树状数组,顾名思义,是长得像树一样的数组。我来上张图。

明显的百度来的对吧ovo。

下面的A数组是原来的数组ovo,而上面的C是你自己开的树状数组。(下文我叫它t)

你可以看到它的管理方式就像把线段树挤到了右边去一样。实际上树状数组和线段树基本没差,但树状数组常数小,好写。可惜的是树状数组对询问的要求比较苛刻,这个我们稍后再谈。

树状数组最爱用的一个函数叫lowbit。这个函数要自己写,代码超级超级短。

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

我们设原来的x在二进制下,末尾的连续0有a个,则这个函数返回的值是2^{a}

有什么用呢?我们来观察一下。如果我们修改了结点1,那受其影响的结点有1,2,4,8。

然后你会惊讶的发现

1+lowbit(1)=2;

2+lowbit(2)=4;

4+lowbit(4)=8;

再举个栗子,我修改了结点3,受之影响的有3,4,8;

3+lowbit(3)=4;

4+lowbit(4)=8;

哈,也就是说每当我们修改了一个结点,也就是单点修改的时候,update就可以直接用当前序号加上lowbit(当前序号)就可以啦!

上代码。

void update(int id,int key){//id是结点号,key是本次增加的值,即a[id]=a[id]+key;
    while(id<=n){
        t[id]+=key;
        id+=lowbit(id);        
    }//总结点数n
}

那查询呢?

有了之前的经验,你会更轻松地发现

8-lowbit(8)=0;

7-lowbit(7)=6;

6-lowbit(6)=4;

……

发现什么了?你会轻松地发现,任何一个数减去自己的lowbit,得到的就是自己管辖不到左边的最近的结点号。参照下图。

也就是说,查询1~x的前缀和变得不再困难,代码超级简单。

int query(int i){
    int ans=0;
    while(i>0){
        ans+=t[i];
        i-=lowbit(i);
    }
    return ans;
}

总结一下,树状数组包含的操作主要有update,query,lowbit。前两个操作因题而异。

下面我们说一下树状数组的三个用途。

1.单点修改,区间查询

把上文的代码拼到一起就可以了ovo。查询l到r的区间和时,直接输出query(r)-query(l-1);(差分的思想嘛)。

2.区间修改,单点查询。

这个稍稍复杂一点。因为这里要用到差分的思想。

因为我修改一个数,会影响到它上面的。所以区间修改的时候,我是不是只需要修改一个呢?

答案当然是肯定的,要不然我就不用写博客了。

我想让l到r的所有数加上x,直接update(l,x),再update(r+1,-x);赤裸裸的差分。

查询不变,就可以啦,读者要思考哦。

3.求逆序对

这个是高中后大佬们最爱用的求逆序对方法。当然一般都要用到离散化,而蒟蒻的我又没有学并且现在懒得自学了QAQ所以下次再说。

简要提一下就是加入一个数把大于它的所有lowbit都加一:a[i+lowbit[i]]++;然后最后查的时候用总数减去查出数就是逆序对数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值