树状数组

首先,我们阐明一下为什么我们要学习树状数组,因为这个东西在某些方面来说要比线段树好一些,首先就是他自己比较好写,第二就是他在解决某些简单问题时,时间复杂度会比线段树更小一些。

下面先来上一篇代码:
大家可以认真看一下
题目传送门(单点修改和区间查询)

#include<iostream>
#include<cstring>
#define lowbit(x) x&-x
using namespace std;
int m,n;
long long node[500005];
void add(long long num,int x)
{
    while(x<=n)
    {
        node[x]+=num;
        x+=lowbit(x);
    }
}
long long  query(int x)
{
    long long ans;
    ans=0;
    while(x)
    {
        ans+=node[x];
        x-=lowbit(x);
    }
    return ans;
}
int main()
{
    memset(node,0,sizeof(node));
    std::ios::sync_with_stdio(false);
    cin>>n>>m;
    long long a;
    for(int i=1;i<=n;++i)
    {
        cin>>a;
        add(a,i);
    }
    //int a;
    int c,d;
    long long e;
    for(int i=1;i<=m;++i)
    {
        cin>>a;
        if(a==1)
        {
            cin>>c>>e;
            add(e,c);
            //add(-e,d+1);
        }
        else
        {
            cin>>c>>d;
            cout<<(query(d)-query(c-1))<<endl;
        }
    }
    return 0;
}

那么下面我们就来简单的讲解一下树状数组,代码其实非常的简单,也非常的好理解,唯一不好理解的就是,lowbit和它建立的树的模样,在这里为大家提供一张图和一个实现lowbit操作的简单计算器,大家可以体会一下lowbit的神奇。

代码

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n;
	cout<<"Welcome to use the tool of lowbit"<<endl;
	for(;;)
	{
		cin>>n;
		cout<<(n&(-n))<<endl;
		cout<<"_________________________________"<<endl;
	}
	return 0;
}

图片

这里写图片描述

关键

+lowbit跳到自己的爸爸,-lowbit跳到自己管辖范围的前面(这样就实现了求前缀和,当然你也可以存差分的东西这样就可以存当前位置节点的大小,后面会讲解)

好了相信你在自己做了一些实验后已经感受到了lowbit的神奇,那么下面我们就来证明一下,博主很懒,这里粘一篇感觉写的很好的证明。

首先明白一个概念,计算机中-i=(i的取反+1),也就是i的补码 
而lowbit,就是求(树状数组中)一个数二进制的1的最低位,例如01100110,lowbit=00000010;再例如01100000,lowbit=00100000。 
所以若一个数(先考虑四位)的二进制为abcd,那么其取反为(1-a)(1-b)(1-c)(1-d),那么其补码为(1-a)(1-b)(1-c)(2-d)。 
如果d为1,什么事都没有-_-|||但我们知道如果d为0,天理不容2Σ( ° △ °|||)︴ 
于是就要进位。如果c也为0,那么1-b又要加1,然后又有可能是1-a……直到碰见一个为补码为0的bit,我们假设这个bit的位置为x 
这个时候可以发现:是不是x之前的bit的补码都与其自身不同?,x之后的补码与其自身一样都是0? 
例如01101000,反码为10010111,补码为10011000,可以看到在原来数正数第五位前,补码的进位因第五位使其不会受到影响,于是0&1=0,; 
但在这个原来数“1”后,所有零的补码都会因加1而进位,导致在这个“1”后所有数都变成0,再加上0&0=0,所以他们运算结果也都是零; 
只有在这个数处,0+1=1,连锁反应停止,所以这个数就被确定啦O(∩_∩)O 
所以and以后只有x这个bit是一……

下面这里有一道升级版的题目,大家可以看一看。

题目传送门(区间修改+单点求值)

这里在上代码之前还是要做一番讲解,这个东西需要用到差分的思想,下面是一个关于差分思想的简介:

来介绍一下差分

设数组a[]={1,6,8,5,10},那么差分数组b[]={1,5,2,-3,5}

也就是说b[i]=a[i]-a[i-1];(a[0]=0;),那么a[i]=b[1]+....+b[i];(这个很好证的)。

假如区间[2,4]都加上2的话

a数组变为a[]={1,8,10,7,10},b数组变为b={1,7,2,-3,3};

发现了没有,b数组只有b[2]和b[5]变了,因为区间[2,4]是同时加上2的,所以在区间内b[i]-b[i-1]是不变的.

所以对区间[x,y]进行修改,只用修改b[x]与b[y+1]:

b[x]=b[x]+k;b[y+1]=b[y+1]-k;

相信认真看了上面我写的“关键”你就可以知道这玩意儿怎么做了,下面上标程:

#include<iostream>
#include<cstring>
#define lowbit(x) x&-x
using namespace std;
int m,n;
long long node[500005];
void add(long long num,int x)
{
    while(x<=n)
    {
        node[x]+=num;
        x+=lowbit(x);
    }
}
long long  query(int x)
{
    long long ans;
    ans=0;
    while(x)
    {
        ans+=node[x];
        x-=lowbit(x);
    }
    return ans;
}
int main()
{
    memset(node,0,sizeof(node));
    std::ios::sync_with_stdio(false);
    cin>>n>>m;
    long long a,last;
    last=0;
    for(int i=1;i<=n;++i)
    {
        cin>>a;
        add(a-last,i);
        last=a;
    }
    //int a;
    int c,d;
    long long e;
    for(int i=1;i<=m;++i)
    {
        cin>>a;
        if(a==1)
        {
            cin>>c>>d>>e;
            add(e,c);
            add(-e,d+1);
        }
        else
        {
            cin>>c;
            cout<<query(c)<<endl;
        }
    }
    return 0;
}

后面还是会更新的······

原文地址:https://www.cnblogs.com/mudrobot/p/13328852.html