树状数组粗略讲解

来源:互联网 发布:薛之谦话题精华知乎 编辑:程序博客网 时间:2024/03/29 15:24

优势:对于普通数组,其修改的时间复杂度位O(1),而求数组中某一段的数值和的时间复杂度为O(n),因此对于n的值过大的情况,普通数组的时间复杂度我们是接受不了的。在此,我们引入了树状数组的数据结构,它能在O(logn)内对数组的值进行修改和查询某一段数值的和。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值。这种数据结构(算法)并没有C++和Java的库支持,需要自己手动实现。在竞赛中被广泛的使用。树状数组和线段树很像,但能用树状数组解决的问题,基本上都能用线段树解决,而线段树能解决的树状数组不一定能解决。但相比较而言,树状数组效率要高很多。

下面就开始举例,以a数组来存储输入的数,c数组来表示A数组形成的树状数组。

这里写图片描述

我们来观察这个图

c1=a1

c2=c1+a2=a1+a2

c3=a3

c4=c2+c3+a4=a1+a2+a3+a4

c5=a5

c6=c5+a6=a5+a6

c7=a7

c8=c4+c6+c7+a8=a1+a2+a3+a4+a5+a6+a7+a8

c9=a9

这里有个规律

设节点编号为i,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ai

所以很明显:Ci= A(i – 2^k + 1) + … + Ai

而这个2^k有种快速求法

int lowbit(int x){  return x&(x^(x–1));  }  

利用机器的补码原理也可以写成这样:

int lowbit(int x){       return x&(-x);  //我们做题大部分也这么写的}  

下面我们可以随便举几组数据来判断下它的正确性

比如i=6 它的二进制表示为110 所以k=1(因为110末尾只有1个0) 2^k=2 6&(-6)=2

C6=A5+A6

又比如i=7 它的二进制表示为111 所以k=0(末尾没有0) 2^k=1 7&(-7)=1

C7=A7

又比如i=8 二进制表示为1000 所以k=3 2^k=8
8&(-8)=8

C8=A1+A2+A3+A4+A5+A6+A7+A8

void add(int i,int x)//添加元素构成树状数组 i为位置 x为元素{    while(i<=n)    {        c[i]+=x;        i+=lowbit(i);    }}

当取一段数的和的时候

int sum(int i)//i为位置  如果你想取u,v之间的和 即就是sum(u)-sum(v-1){     int k=0;     while(i>0)     {         k+=c[i];         i-=lowbit(i);     }     return k;}

所以树状数组的模板代码如下:

#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <map>#include <cmath>#include <queue>#include <string>#include <vector>#include <set>using namespace std;#define sc(x) scanf("%d",&x)#define FOR(i,n,o) for(int i=o;i<=n;i++)#define pr(x) printf("%d\n",x)#define lcr(a,b) memset(a,b,sizeof(a))const int maxn=10e2+10;int n,m;int c[maxn];int lowbit(int x){    return x&(-x);}void add(int i,int x){    while(i<=n)    {        c[i]+=x;        i+=lowbit(i);    }}int sum(int i){     int k=0;     while(i>0)     {         k+=c[i];         i-=lowbit(i);     }     return k;}int main(){    while(~sc(n))    {        int a;        lcr(c,0);        FOR(i,n,1)        {            sc(a);            add(i,a);        }        sc(m);        int u,v;        int ans=0;        while(m--)        {             scanf("%d%d",&u,&v);             ans=sum(v)-sum(u-1);             pr(ans);//v u之间的和        }    }    return  0;}

END!!!!!!!!!!!!!!!!!!!!!!!!!!

1 0