树状数组

Posted 邪童的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树状数组相关的知识,希望对你有一定的参考价值。

树状数组是一种能够同时高效支持数组查询与修改操作的数据结构

树状数组, 可以高效地计算数列前缀和, 它的查询(求前缀和) 和更新(修改) 操作都可以在 O(logn) 的时间完成


tr[i] 存储以 i 为终点, 长度为 lowbit(i) 的区间

修改: for( int i = x ; i <= n ; i += lowbit(i) ) tr[i] += c

查询: for( int i = x ; i ; i -= lowbit(i) ) sum += tr[i]


//树状数组操作

//返回非负整数x在二进制表示下最低位1及其后面的0构成的数值
int lowbit (int x)

    return x&-x;


//将序列中第x个数加上k
void add (int x,int k)

    for(int i=x;i<=n;i+=lowbit(i))tr[i]+=k;


//查询序列前x个数的和
int ask (int x)

    int sum=0;
    for(int i=x;i;i-=lowbit(i))sum+=tr[i];
    return sum;


//O(n)的建树方法
void build ()

    for(int x=1;x<=n;x++)
    
        tr[x]=a[x];
        for(int i=x-1;i>=x-lowbit(x)+1;i-=lowbit(i))
            tr[x]+=tr[i];
    




树状数组的应用

① 点更新, 区间查询

② 区间更新, 点查询

使用差分, 维护差分数组 d[i] = a[i] - a[i-1]

区间更新变成了 [l,r] 两端 lr 的更新, 点查询也就变成了 [1,x] 的区间查询

③ 区间更新, 区间查询

使用差分, 维护差分数组 c[i] = a[i] - a[i-1]

区间更新变成了 [l,r] 两端 lr 的更新

对于求解一个 S = a[1,x] 的前缀和, 有:

S = a[1] + a[2] + \\(\\cdots\\) + a[x]

\\(\\quad\\) = c[1] + (c[1] + c[2]) + \\(\\cdots\\) + (c[1] + c[2] + ... + c[x])

\\(\\quad\\) = x*c[1] + (x-1)*c[2] + \\(\\cdots\\) + 1*c[x]

\\(\\quad\\) = (x+1) * (c[1] + c[2] + ... + c[x]) - (1*c[1] + 2*c[2] + ... + x*c[x])

因此可以使用另一个辅助数组 D 来维护 d[i] = i*c[i] 的前缀和


//区间更新,区间查询模板

int n;
int a[N],tr1[N],tr2[N];
//a[]存储原数组,b[i]=a[i]-a[i-1]为a的差分数组,tr1[i]维护b[i]的前缀和,tr2[i]维护i*b[i]的前缀和

int lowbit (int x)

    return x&-x;


void add (int tr[],int x,int c)

    for(int i=x;i<=n;i+=lowbit(i))tr[i]+=c;


int sum (int tr[],int x)

    int res=0;
    for(int i=x;i;i-=lowbit(i))res+=tr[i];
    return res;


int prefix_sum (int x)	//求a[1,x]的前缀和

    return sum(tr1,x)*(x+1)-sum(tr2,x);


int main()

    for(int i=1;i<=n;i++)
    
        int b=a[i]-a[i-1];	//差分数组b[i]=a[i]-a[i-1]
        add(tr1,i,b);		//tr1[]维护b[i]的前缀和
        add(tr2,i,b*i);		//tr2[]维护i*b[i]的前缀和
    



树状数组的树状数组的经典操作

参考技术A

给定一个初始值都为0的序列,动态地修改一些位置上的数字,加上一个数,减去一个数,或者乘上一个数,然后动态地提出问题,问题的形式是求出一段数字的和.
算法分析
如果直接做的话,修改的复杂度是O(1),询问的复杂度是O(N),M次询问的复杂度是M*N.M,N的范围可以有100000以上,所以这样做会超时,但是如果用线段树的话,还是很不错的! 有很多线段树能实现但树状数组却实现不了的题目。
线段树解法分析
可以看出,这棵树的构造用二分便可以实现.复杂度是2*N.
每个结点用数组a来表示该结点所表示范围内的数据之和.
修改一个位置上数字的值,就是修改一个叶子结点的值,而当程序由叶子结点返回根节点的同时顺便修改掉路径上的结点的a数组的值.
对于询问的回答,可以直接查找i..j范围内的值,遇到分叉时就兵分两路,最后在合起来.也可以先找出0..i-1的值和0..j的值,两个值减一减就行了.后者的实际操作次数比前者小一些.
这样修改与维护的复杂度是logN.询问的复杂度也是logN,对于M次询问,复杂度是MlogN.
然而不难发现,线段树的编程复杂度大,空间复杂度大,时间效率也不高!!!!
所以我们来想用树形数组来实现:
那么,何为树形数组呢??
C数组就是树状数组,a数组是原数组;
对于序列a,我们设一个数组C定义C[t] = a[t – 2^k + 1] + … + a[t],k为t在二进制下末尾0的个数。
K的计算可以这样:
2^k=t and (t xor (t-1))
以6为例
(6)10=(0110)2
xor 6-1=(5)10=(0101)2
(0011)2
and (6)10=(0110)2
(0010)2
所以问题变的很简单,重要写几个函数就可以了;
求2^k的函数代码如下: int Lowbit(int t)return t&(t^(t-1));FunctionLowbit(t:longint):longint;BeginLowbit:=tand(txor(t-1));End;求1 -- end和的函数代码如下: intSum(intend)intsum=0;while(end>0)sum+=in[end];end-=Lowbit(end);returnsum;FunctionSum(tail:longint):longint;Vars:longint;Begins:=0;whiletail>0dobegininc(s,in[tail]);dec(tail,Lowbit(tail));end;sum:=s;End;对某位进行操作函数如下(以加法为例) void plus(int pos,int num)while(pos<=n)in[pos]+=num;pos+=Lowbit(pos);Procedureplus(p,num:longint);Beginwhilep<=ndobegininc(in[p],num);inc(p,Lowbit(p));end;End.有了这三个函数整个树形数组也就基本构建成功啦!!
对于刚才的一题,每次修改与询问都是对C数组做处理.空间复杂度有3N降为N,时间效率也有所提高.编程复杂度更是降了不少.
对于lowbit可有优化 P :lowbit:=X AND(not x+1)或 X AND (-X); C: lowbit:=x &(-x)或 x & (~x+1); 求n个数中 k组提问 从t到t1个数字之和
pascal代码 programBIT_test;uses sysutils;var a,c:array[1..100000]oflongint;    n:longint;    ch:char;    i,k,t,t1:longint;    ti:double;//计时用functionLow_bit(x:longint):longint;//取出x在二进制下最右边一个1begin  exit(x and (-x));end;procedure Modify(pos,x:longint);//改变数据begin  while pos<=n do begin    inc(c[pos],x);    inc(pos,low_bit(pos))  end;end;function sum(pos:longint):longint;//1..pos求和begin  sum:=0;  while pos>0 do begin    inc(sum,c[pos]);    dec(pos,low_bit(pos));  end;end;begin  readln(n);  for i:=1 to n do begin    read(a[i]);    Modify(i,a[i]);  end;  readln;  ti:=now;  readln(k);  for i:=1 to k do begin    readln(t,t1);    writeln(sum(t1)-sum(t-1));  end;  writeln((now-ti)*86400*1000:0:0,'MS');end.

以上是关于树状数组的主要内容,如果未能解决你的问题,请参考以下文章

树状数组的树状数组的经典操作

树状数组模板

数据结构之树状数组从零认识树状数组

树状数组 / 二维树状数组

浅谈树状数组

Bit的树状数组