洛谷 P3374 树状数组【模板1】

树状数组是将每个元素加到他后面的元素上,每次向后走lowbit(x)位,树状数组中存的是该位之前的前缀和,所以求某个区间和时只需要将区间两个端点的前缀和相减即可,但是如果直接暴力求前缀和一定会T,所以要引入一个新东西 ->lowbit

引用大佬的话:

我先说个叫lowbit的东西,lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1,比如10的二进制是1010,则lowbit(k)=lowbit(1010)=0010(2进制),介于这个lowbit在下面会经常用到,这里给一个非常方便的实现方式,比较普遍的方法lowbit(k)=k&-k,这是位运算,我们知道一个数加一个负号是把这个数的二进制取反+1,如-10的二进制就是-1010=0101+1=0110,然后用1010&0110,答案就是0010了!明白了求解lowbit的方法就可以了,继续下面。介于下面讨论十进制已经没有意义(这个世界本来就是二进制的,人非要主观的构建一个十进制),下面所有的数没有特别说明都当作二进制。   

上面那么多文字说lowbit,还没说它的用处呢,它就是为了联系a数组和c数组的!ck表示从ak开始往左连续求lowbit(k)个数的和,比如c[0110]=a[0110]+a[0101],就是从110开始计算了0010个数的和,因为lowbit(0110)=0010,可以看到其实只有低位的1起作用,因为很显然可以写出c[0010]=a[0010]+a[0001],这就为什么我们任何数都只关心它的lowbit,因为高位不起作用(基于我们的二分规则它必须如此!),除非除了高位其余位都是0,这时本身就是lowbit。

 

既然关系建立好了,看看如何实现a某一个位置数据跟改的,她不会直接改的(开始就说了,a根本不存在),她每次改其实都要维护c数组应有的性质,因为后面求和要用到。而维护也很简单,比如更改了a[0011],我们接着要修改c[0011],c[0100],c[1000],这是很容易从图上看出来的,但是你可能会问,他们之间有申明必然联系吗?每次求解总不能总要拿图来看吧?其实从0011——>0100——>1000的变化都是进行“去尾”操作,又是自己造的词--'',我来解释下,就是把尾部应该去掉的1都去掉转而换到更高位的1,记住每次变换都要有一个高位的1产生,所以0100是不能变换到0101的,因为没有新的高位1产生,这个变换过程恰好是可以借助我们的lowbit进行的,k +=lowbit(k)。

       好吧,现在更新的次序都有了,可能又会产生新的疑问了:为什么它非要是这种关系啊?这就要追究到之前我们说c8可以看作a1~a8的左半边和+右半边和……的内容了,为什么c[0011]会影响到c[0100]而不会影响到c[0101],这就是之前说的c[0100]的求解实际上是这样分段的区间 c[0001]~c[0001] 和区间c[0011]~c[0011]的和,数字太小,可能这样不太理解,在比如c[0100]会影响c[1000],为什么呢?因为c[1000]可以看作0001~0100的和加上0101~1000的和,但是0101位置的数变化并会直接作用于c[1000],因为它的尾部1不能一下在跳两级在产生两次高位1,是通过c[0110]间接影响的,但是,c[0100]却可以跳一级产生一次高位1。

不懂的话自己去手模,再画个树状图模拟数组中存的值,大概就理解得差不多了

要记得   lowbit(x)=x & -x

UPFATE 19-8-22:这份代码太丑了,去看我改过的吧   模板2

代码

 

//By Acer.mo
#include
#include
using namespace std;
const int maxa=10000000;
int a[maxa+10]={0};//初始的存储数组 
int l_b[maxa+10]={0};//树状数组 
int n,i,j,m;
int lowbit(int x)//求x的lowbit值 
{
    return x&-x;
} 
void add(int k,int num)//lowb丢值函数 /构建树形 /后期单点加值 
{
        for(;k<=n;k+=lowbit(k))//将lowb数组中所有包含第k位数的位置加上num 
        l_b[k]+=num;

}
int query(int x)//求和函数,求第x位前的和 
{
    int sum=0;
    for (;x;x-=lowbit(x))
    {
        sum+=l_b[x];
    }
    return sum;
}
int main()
{
    scanf("%d %d",&n,&m);//scanf读入,不然会tle 
    for (i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);//读入初始数组 
        add(i,a[i]);//将a[i]加入lowb数组 
    }
    int s,a,b;
    while (m--)
    {
        scanf("%d %d %d",&s,&a,&b);
        if (s==1)
        {
            add(a,b);
        }
        else 
        {
            cout<

 

 

 

你可能感兴趣的:(数据结构-树状数组)