树状数组

 一、模板

默认预设:

a[maxn]  ———原始数组

c[maxn]  ———新建的树状数组。对于任意一个位置 i ,树状数组 c[i] 里的值就是"它能管理到的所有位置上,原数组的值之和"

基本函数一:lowbit

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

 x+lowbit(x),就可访问到x的上一级父节点;

 

基本函数二:upgrade

当我们修改a数组中的某一个值时  应当如何更新c数组呢?答案就在这里

upgrade函数,用于单点更新:当原数组a中,下标为x的位置被+k的时候,只需要"能管理到x的所有位置"+k就行

调用函数:upgrade(c,x,k)

void upgrade(int *arr,int x,int n)//n为:树状数组的节点总数,也是原数组的节点总数
{                    //k为:当前被更新的节点,在原数组a中的下标值
    while(x<=n)
    {
        arr[x]+=k;
        x=x+lowbit(x);
    }
}

 

ps:经常用这个函数,来构建初始树状数组。一边读入原数组a,一遍就更新树状数组c

构建树状数组的时候:自底向上,每一个数都把它能做的贡献做了。

        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            int k=i;
            while(k<=n)
            {
                c[k]+=a[i];
                k=k+lowbit(k);
            }

        }
View Code

 

 

基本函数三:getsum

用于区间查询:查询原数组 a 从1~x 区间上的和。利用c[i]数组,求a数组中前i项的和。

利用树状数组对a求和的时候:自顶向下

 调用函数:getsum(c,x)

int getsum(int *arr,int x)
{
    int ans=0;
    while(x>0)
    {
        ans+=arr[x];
        x-=lowbit(x);
    }
    return ans;
}

 

拓展应用:也可以被用来查询任意区间l~r上的和,只需sum(r)-sum(l) 即可。

 

模板使用时的注意点 && 进阶小技巧

1):所有数组,无论是 a [i] , c[i] ,还是 cx [i] 数组,一律从下标 1 开始存起,下标为0的位置不存数据。

      这一点很重要,如果下标从0开始,那么getsum中while(x>0)就必须改成while(x>=0)(因为0号位的数据也要加进去嘛)

      但问题是,0+lowbit(0) ,永远都是0。这样一来,在 0 处就会产生死循环,

2):如何确定,upgrade(c,x,k)中的x、k参数,即:传入哪个下标,每个位置加多少??

      确定传入的下标x:从哪个下标开始,此下标之后所有位置 i 的sum(i),都会受到a[x]被更新的影响?

  确定每一个结点加的数k:此次focus的结点,对最终答案sum的贡献,有多大?

      一般而言,k都是原数组a[i]的数据值本身。

3):getsum(c,x)得出的累加和,是包含了原数组中a[x]的。(末尾数据也算进了getsum返回的累加和)

4):到底是先upgrade,还是先getsum?:这取决于,调用sum(x)时,你想要求的,是“小于”还是“小于等于”x的累加和。

     如果有“等于”,要把x位置加上去,那就先upgrade,先更新过再加;否则,如果不要把x位置加上去,那就先getsum,趁

   着还没x位置还没被加到数组上去,先sum

 

二、基本题型以及三个基本模型)

模型一:原数组a[i]的数据域,存储的就是数值本身,用于sum来求:区间l到r上的所有数字的累加和

模型二:原数组a[i]的数据域,存储的是“差分值”,用于sum来求:插段求点

模型三:原数组a[i]的数据域,不存储具体的数值,而存储“数值 i 出现的次数”,用于sum来求:“ 小于 ‘等于’ i 的数字,到目前为止,出现的次数之和”

 

一、插点求段  (更新单点,查询区间)                                 ——————模型一

我的通常做法是:单点向上更新,sum(代表1~n的和)向下查询

专题10 1001

 

二、插段求点(更新的是 “区间,查询的是单点”————成段更新,单点查询)  ——————模型二

差分思想。

原数组a[i],存储的是,第i个数值和第i-1个数值之差。

把“更新区间”转换成“更新两个点”

http://www.cnblogs.com/iris001999/articles/8926598.html

 

三、插段求段                                                      ————模型二

 依旧是差分。

 

 小应用:

一、用树状数组求逆序对                                   ————模型三

由此引出树状数组的另一种常见模型:

 原数组a[i],不存储具体的数值,而存储“数值 i 出现的次数”。 

具体的数值,由下标表示。数组下标具有实际意义:代表数值大小。

例如,"a[i]"存储的值就表示,数值 i 出现的次数。

然后,构建原数组a[i]的树状数组c[i] , 最后sum(c , i)的结果,就代表:“ 小于 ‘等于’ i 的数字,到目前为止,出现的次数之和”。 

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1556

扔下大佬的题解:https://www.cnblogs.com/xiongmao-cpp/p/5043340.html

AC代码:https://paste.ubuntu.com/p/N9SQrGK5Kx/

 

三、树状数组综合应用:

树状数组综合题的解题思路:

先想总体思路,先把最终的表达式子推出来。

因为树状数组归根结底,只是一种 累加的“具体实现”上的优化,并不是思路的东西。

然后看着你最终得出的那个式子

只要发现:有累加形式,并且累加式子里的每一项,是有规律的,是可以给出“通项公式”的,就代表此处可以优化。

而且,可以构建多个树状数组。多出来的树状数组,不会增加很多复杂度,只需要在更新的时候“顺带着更新一下多出来的其他树状数组”,就可以了。

看自己需求,需要几个树状数组,就维护几个树状数组就可以了。

经典题目:

 

 

posted on 2018-04-23 16:14  _isolated  阅读(128)  评论(0)    收藏  举报