【入门向】树状数组概念理解与例题实现

数据结构,是OIer们永远没法避免的一个难关,而简单数据结构,则是这一切冒险的开始。html

下面咱们就从最简单的树状数组开始学习简单数据结构吧!ios

概念重要性:较重要数组

概念复杂性:较低数据结构

 

概念:树状数组函数

讲解连接:树状数组学习

理解:lowbit(x)能够计算出以x为右端点的由t[x]存储的数据和所覆盖的区间宽度(从x-lowbit(x)+1到x)spa

          将数据输入后构建树状数组(因为t数组默认初始化为0,因此能够直接使用修改的Add函数对t数组对应位置进行修改)指针

     每次修改(插入)完一个元素后,都应该对其后求和时包含了它的元素进行修改code

     查询时则经过跳跃查询的方式查询从1到pos的区间和(能够经过前缀和求区间和的作法来求从s到e的区间和)htm

 

例题:

[Luogu P3374] (模板)数状数组1

[Luogu P3368] (模板)数状数组2 //PS:这题须要使用差分思想,对思惟有必定锻炼做用

 

具体示例:

 P3374: 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define lowbit(x) x&(-x)//大名鼎鼎的lowbit(函数) 
const int MAXN = 600000; 
using namespace std;
int t[MAXN],size;
void Add(int pos,int n)//给pos位置上的数加上n 
{
    while(pos<=size)//向上循环更新包括pos节点的各节点的值(每次向上跳lowbit(x)) 
    {
        t[pos]+=n;//更新当前指向的节点的值 
        pos+=lowbit(pos);//移动"指针"(然而博主并不会写指针...) 
    }
}
long long Qry(int pos)//查询从1到pos的全部数之和 
{
    long long sum=0;
    while(pos)//向下循环求和 
    {
        sum+=t[pos];//将当前节点的值加入总和中 
        pos-=lowbit(pos);//跳过当前pos位置所求出和的部分,对以前还未求和的数字进行处理 
    }
    return sum;
}
int main()
{
    int n;
    ios::sync_with_stdio(false);//关闭流同步... 
    memset(t,0,sizeof(t));//初始化数组 
    cin>>size>>n;//输入数组大小和操做次数 
    int num;
    for(int i=1;i<=size;i++)
    {    
        cin>>num;//输入第i位的值 
        Add(i,num);//将num加到第i位上,并向上更新全部包括i的节点 
    }
    for(int i=1;i<=n;i++)//处理n次操做 
    {
        int s,e,ops;
        cin>>ops>>s>>e;//ops=1 ==> 将s处的值加上e ; ops=2 ==> 查询从s到e的值(闭区间[s,e]) 
        if(ops==1) Add(s,e);//更新(修改) 
        else cout<<Qry(e)-Qry(s-1)<<endl; //查询([s,e]闭区间能够表示为从e到1与从s-1到1的和之差) 
    }
    return 0;
}

 

P3668(无注释)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define lowbit(x) ((x!=0)?x&(-x):1)
const int MAXN = 600000;
using namespace std;
int d[MAXN]={0},num[MAXN]={0},n;
int Add(int pos,int num)
{
    while(pos<=n)
    {
        d[pos]+=num;
        pos+=lowbit(pos);
    }
}
int query(int pos)
{
    int sum=0;
    while(pos)
    {
        sum+=d[pos];
        pos-=lowbit(pos);
    }
    return sum;
}
int main()
{
    int m,x,y,k,opr;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&num[i]);
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&opr);
        if(opr==1)
        {
            scanf("%d %d %d",&x,&y,&k);
            Add(x,k); Add(y+1,-k);
        }
        else
        {
            scanf("%d",&x);
            printf("%d\n",num[x]+query(x));
        }
    }
    return 0;
}

还有大量的树状数组的经典例题,这里便不一一列举了  (话说NOIP2017 [就是那场D1T1是结论题的傻×比赛(差点被逼到退役...)] D2最后一题 正解便是树状数组...)

重点:树状数组只适用于维护有区间可减性的数据(如求和/逆序对等问题),若是维护的数据不具有这种性质,通常使用这种方法即是错误的,这是咱们就须要求助于其它的数据结构啦...

   树状数组对于线段树的优势在于代码量少,不易犯错(对新手较为友善),同时速度比线段树快,空间占用量比线段树少(1倍VS4倍);缺点则在于原理复杂(晦涩难懂),且功能较为局限.

(可是用来卡常数却是还不错)

相关文章
相关标签/搜索