防止自己刚上完课就忘了

树状数组初步讲解_数组

先看这玩意,怎么写?

1,写一个简单的一维数组,对于第一个操作直接a[i]+=x,对于第二个暴力便利,然后喜提30分

2,树状数组

3,线段树

现在我们先讲第二个

对于一个数组如:a[]={2,1,4,3,7,9,-1,6},如何求出其所有元素的和呢

反正这样可知道和为31

树状数组初步讲解_ci_02

如果数组的元素有九个就是这样

树状数组初步讲解_ci_03


发现没,很像一棵树(你当他像就完了)

假如我们设这些数字的索引为1-8,则可以用logn的时间复杂度解出每一个元素前几项的和(包括自己)

索引为8的元素(6)前面的总和就是31

索引为7的元素(-1)前面的就是总和10+16+(-1)=25

索引为5的元素(7)前面的就是总和10+7=17

如果只考虑加法运算的话,我们发现只有标红的元素有用(是不是很神奇)

树状数组初步讲解_树状数组_04

大家可以举出几个例子,至于原理是什么,就类似于二进制只有1和0(0代表没,1代表有),如果出现了2直接表示为大一位的一就好了,理解不了就别理解(二进制的(10)和十进制的(2))

发现没,不标红的元素个数正好等于原数组的元素个数

所以对于树状数组,直接开和原数组长度相等的空间,往里头填就好了

那怎么求解呢(现在的树状数组为2,3,4,10,7,16,-1,31)


假如我们要求索引为6的元素(9)以前(包括自己)的元素和,那和为16+0,即树状数组索引为6的元素和索引为4的元素相加(二进制的6为110,二进制的4为100)


假如我们要求索引为3的元素(4)以前(包括自己)的元素和(二进制的3为11),那和为3+4,即树状数组索引为3的元素和索引为1的元素相加(二进制的3为11,二进制的2为10)


假如我们要求索引为7的元素(-1)以前(包括自己)的元素和(二进制的7为111),那和为10+16+(-1),即树状数组索引为7的元素和索引为6的元素和和索引为4的元素相加(二进制的7为111,二进制的6为110,二进制的4为100)


发现没,只要每次将要求的索引的二进制抹去末尾的1(111->110->100),再将这些处理过的索引对应的元素加起来,所得值就是答案

那我要怎么样抹去末尾的1呢,十进制转二进制再遍历删除再转二进制吗?

nonono 

当当当当lowbit

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

这是啥?

原理自己查(我也不知道,类似于100-99?)反正就是能返回x所对应的二进制数只保留从后往前第一个1的结果

#include <bits/stdc++.h>
using namespace std;
int lowbit(int x)
{
    return x&-x;
}
int main()
{
    cout<<lowbit(4)<<' '<<lowbit(3)<<' '<<lowbit(9)<<' '<<lowbit(8);
}

这个程序输出为:4 1 1 8

因为4,3,9,8这四个数的二进制表达分别为100,11,1001,1000,只保留末尾第一个1的二进制是100,1,1,1000,即十进制的4,1,1,8

那就可以简单的判断要加上那些数字了,让索引每次减去lowbit(索引)就好了(注意别减到0)

int t[MAXN]//用来存树状数组
int getsum(int x)
{
  int sum=0;
  for(int i=x;i!=0;i-=lowbit(i))
  {
    sum+=t[i];
  }
  return sum;
}

要求一段区间的总和值就是getsum(a)-getsum(b)


那怎么写修改值呢

我们注意到,假如我们现在想是索引为3的元素的值+2

只需要让标红的元素都+2就好啦

树状数组初步讲解_树状数组_05

具体怎么实现呢,只需要每次将索引+lowbit(索引)就好了

void pointadd(int idx,int x)//使索引为idx的元素加上x
{
  for(int i=idx;i<=n;i+=lowbit(i))
  {
    t[i]+=x;
  }
}

有人要问了,怎么初始化呢

很简单,直接把数组初始化为0,每次都加就好了


把所有的要素,全部加起来

#include <bits/stdc++.h>
#define MAXN (int)5e5+7
using namespace std;
int a[MAXN],t[MAXN],n,m;
int lowbit(int x)
{
    return x&-x;
}
int getsum(int x)
{
    int sum=0;
    for(int i=x;i;i-=lowbit(i))
    {
        sum+=t[i];
    }
    return sum;
}
void pointadd(int idx,int x)
{
    for(int i=idx;i<=n;i+=lowbit(i))
    {
        t[i]+=x;
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        pointadd(i,a[i]);
    }
    int op,x,k;
    for(int i=1;i<=m;i++)
    {
        cin>>op>>x>>k;
        if(op==2)
        {
            cout<<getsum(k)-getsum(x-1)<<endl;
        }
        else
        {
            pointadd(x,k);
        }
    }
    return EXIT_SUCCESS;
}

就可以做出来了

课下作业:

P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

提示:查分与树状数组结合