树状数组学习小结

BIT(树状数组),很强大,主要体现在时间,空间,编程复杂度都要比segmen tree(线段树)小,缺点就是适用范围比segmen tree小,也就是说它能解决的问题线段树一定能够解决。
搞了我一天,才终于搞会了树状数组以及一些变形的用法。
在这里推荐一片我认为讲的比较清楚的:(http://wenku.baidu.com/link?url=91qLGMGUKpOaaaRPjjY3l_axRBpdNHvTqpKHdIiF38PdC9oCFrFIZpSV2Bw78bEogKYq4-jiXcMdw8-NX1MYeFChGLeTe3zmXOkjrfjTLzy)(下载看更流畅,不知道是不是因为我的电脑弱(。・・)ノ)
入正题。


树状数组的构造原理我这里就不详细谈了,网上很多都有。
就是一个树状数组(以下用C表示)的第i位是其原数组该位与前lowbit(i)位的和,也就是:c[i]=a[i-lowbit(i)+1]+……..+a[i](什么,你不知道lowbit是什么?恭喜恭喜)
以下为树状数组的重要用法,主要是用于求区间的和以及进行区间加减乘除之类。


基本用法:改点求段(即改动区间内一个值,求一段的和)。
口述就是在树状数组当前位置c[i]改动后,其每一个直接父亲节点都要改动,也就是c[i+lowbit(i)]也要改动,直到最后i>n时,才停止改动。

void add(int k,int x)
{
    while (k<=n){//k的限制。
        fre[k]+=x;//fre为树状数组。
        k+=lowbit(k);
    }
}

求和:求L….R的和就是求1….R的和减去1….L-1的和,问题转化为求1…..X的和。

int get(int k)
{
    int sum=0;//sum为和。
    while (k>0){//注意k的界限。
        sum+=fre[k];
        k-=lowbit(k);
    }
    return sum;
}

最基础的操作,记得某些题要离散化。


二:(改段求点,求段)。
树状数组一般不支持改段,但是经过改动也可以支持改段,不过求点求段就比较麻烦。
原数组a[i]不再指i这个位置的值,而是指i…n这一段区间里,每一个位置被加了多少。(这里有点难理解,注意看下面代码。)树状数组c还是在这个a上建立。

改动时,如我要将区间l..r都加1,则我们把a[l]+1,a[r+1]-1。当然,树状数组上也要改动,改动方式和上面是一样。

    fo(i,1,m){
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,1);
        add(y+1,-1);
    } 

求点:例如求第i个位置的数是多少,记为s[i],则s[i]=a[1]+a[2]+…..+a[i]。这个求和便是用树状数组直接求和就行了。

求段:比较麻烦(其实也不是很麻烦啦),因为要求改段后,a数组的定义不一样了。
由上面求点可知每一个点的求法,即s[x]= xi=1a[i] ,感觉很难搞啊,我们来推一下一段区间怎么求。
假设我们现在求s[1]+s[2]+……s[x],记为S。
S=a[1]+a[1]+a[2]+…..+a[1]+a[2]+……a[x]
=a[1]* x+a[2]* (x-1)+…..+a[x]*1
=a[1]* (x+1-1)+a[2]* (x+1-2)+……+a[x]*(x+1-x)
= xi=1a[i](x+1i)
=(x+1)* xi=1a[i] - xi=1a[i]i
我们可以看出,只需要求得a[1]+a[2]+…..a[x]与a[1]* 1+a[2]* 2+…….+a[x]* x便是足以达到目的了,所以我们分别以a[i]与a[i]*i为基础,开两个树状数组。
然后就很好办了,我们要求l…r区间的值,即1…r与1…l-1的值,公式在上面。

这是两个树状数组的修改的代码与求和代码。

void add1(int k,int x)
{
    while (k<=n){
        fre[k].val+=x;//val指a[i]
        k+=lowbit(k);
    }
}
void add2(int k,int x)
{
    while (k<=n){
        fre[k].val2+=x;//val2指a[i]*i
        k+=lowbit(k);
    }
}
int get1(int k)
{
    int sum=0;
    while (k>0)
    {
        sum+=fre[k].val;
        k-=lowbit(k);
    }
    return sum;
}
int get2(int k)
{
    int sum=0;
    while (k>0)
    {
        sum+=fre[k].val2;
        k-=lowbit(k);
    }
    return sum;
}
    fo(i,1,m){
        int x,y;
        scanf("%d%d",&x,&y);
        add1(i,1);
        add2(i,i); //注意,val的修改是x,则val2是x*i。
        add1(y+1,-1);
        add2(y+1,-i);
        scanf("%d%d",&x,&y);
        int sum1=(y+1)*get1(y)-get2(y),sum2=x*get1(x-1)-get2(x-1);
        printf("%d\n",get1(x));//这是求点。
        printf("%d\n",sum1-sum2);//zh这是求段。
    }

树状数组基本是这些,注重理解,以及求段时自己推一推公式,便是可以了,实际上代码上改动并不是很大。
这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值