【学习笔记】树状数组入门

开始学习树状数组了,还是有些小激动的。

P.s.本文持续更新

-----------------------------------------------------------------------------

有以下问题

已知一个长为N的数列,你需要进行下面两种操作共M次:

1.将某一个数加上x

2.求出某区间每一个数的和

数据范围:N<=500000,M<=500000

  怎么做呢?暴力对数组操作?这是可行的,但是N和M都大的可怕,怕是要运行到猴年马月。

  既然无法暴力,那该怎么做呢?这个时候,救世主登场了,它就是:树状数组

  树状数组是什么个东西呢?

树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
这种数据结构(算法)并没有C++和Java的库支持,需要自己手动实现。在Competitive Programming的竞赛中被广泛的使用。树状数组和线段树很像,但能用树状数组解决的问题,基本上都能用线段树解决,而线段树能解决的树状数组不一定能解决。相比较而言,树状数组效率要高很多。

  这次百度终于讲的不是那么玄学了,传送门,可以一看。

 

 

如上图,是一个树状数组的基本结构,每一个紫色长条就是树状数组的一个节点。用来维护一些值。

为什么叫树状数组“树状”呢,是因为树状数组本就是个没有右儿子的二叉树。

不信?下图可能会更直观一些

 

这样子省去右儿子好处主要有两点

  1、节省空间,查询方便

  2、查询速度加快

坏处主要有一点

  1、相比于线段树有局限性,换句话说,在一些题目上无法使用

真正的原理实现请戳这。//讲的超级好!!

再安利一个讲得超级好的网站:传送门(是英文的)

除此之外,树状数组还有一个坑点:求和时sum函数里如果i=0,会死循环

总的来说树状数组一般适用于三类问题:

  1,修改一个点求一个区间(点对区间)

  2,修改一个区间求一个点(区间对点)

  3,求逆序列对以及其他特殊情况

注意:树状数组常常是以一种优化手段出现的,而非主要考点。

此处给出代码:

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=10000;
int bit[maxn+1],n;
int sum(int i)
{
    int s=0;
    while(i>0)
    {
        s+=bit[i];
        i-=i&-i;//lowbit
    }
    return s;
}
void add(int i,int x)
{
    while(i<=n)
    {
        bit[i]+=x;
        i+=i&-i;
    }
}
int main()
{
    /*do something*/
}

 

-------------------------------------以下是练习题---------------------------------

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

分析:没错,裸的模板题。

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 const int maxn=500050;
 5 int bit[maxn+1],n;
 6 long long sum(int i)
 7 {
 8     long long s=0;
 9     while(i>0)
10     {
11         s+=bit[i];
12         i-=i&-i;
13     }
14     return s;
15 }
16 void add(int i,int x)
17 {
18     while(i<=n)
19     {
20         bit[i]+=x;
21         i+=i&-i;
22     }
23 }
24 int main()
25 {
26     int m,d,x,y,p;
27     scanf("%d%d",&n,&m);
28     for(int i=1;i<=n;i++)
29     {
30         scanf("%d",&p);
31         add(i,p);
32     }
33     for(int i=0;i<m;i++)
34     {
35         scanf("%d%d%d",&d,&x,&y);
36         if(d==1) add(x,y);
37         else printf("%lld\n",sum(y)-sum(x-1));
38     }
39     return 0;
40 }
View Code

 

 

洛谷P3368 【模板】树状数组 2

分析:题解里kanate_saikou讲得非常好,推荐一看。思想是用差分。坑点:要快读,不然会Tle 3个点

 1 #include <cstdio>
 2 #include <iostream> 
 3 #include <algorithm>
 4 using namespace std;
 5 const int maxn=500005;
 6 int bit[maxn],n,m,a[maxn];
 7 int in()
 8 {
 9     int res=0,x=1;
10     char c=' ';
11     while((c<'0'||c>'9') && (c!='-')) c=getchar();
12     if(c=='-') x=-1;
13     else res=c-'0';
14     while((c=getchar())>='0'&&c<='9')
15     {
16         res=(res<<3)+(res<<1)+c-'0';
17     }
18     return res*x;
19 }
20 int sum(int i)
21 {
22     int sum=0;
23     while(i>0)
24     {
25         sum+=bit[i];
26         i-=i&-i;
27     }
28     return sum;
29 }
30 int add(int i,int x)
31 {
32     while(i<=n)
33     {
34         bit[i]+=x;
35         i+=i&-i;
36     }
37 }
38 int main()
39 {
40     n=in();m=in();//printf("%d\n%d",n,m);
41     for(int i=1;i<=n;++i) {
42         a[i]=in();
43         add(i,a[i]-a[i-1]);   //直接将树状数组存为差分后数列 #**b[i]=a[i]-a[i-1]**#
44     }
45     for(int p=1;p<=m;++p) {
46         int op,x,y,k;
47         op=in();
48         if(op==1) {
49             x=in();y=in();k=in();
50             add(x,k); add(y+1,-k);   // #**b[x]+=d,b[y+1]-=d**#
51         }
52         if(op==2) {
53             x=in();
54             int ans=sum(x);   //#**bit[i]=sigma(k=1 to i) b[k]**#
55             cout<<ans<<endl;
56         }
57     }
58     return 0;
59 }
View Code

 

posted @ 2017-10-06 23:56  noble_(noblex)  阅读(298)  评论(0编辑  收藏  举报
/* */