树状数组

来源:互联网 发布:国际米兰数据 编辑:程序博客网 时间:2024/05/29 17:27

树状数组(BIT)
解决问题,缩减以下操作的算法复杂的,减少查询时间
(1)add(i,j),实现a[i]=a[i]+j
(2)Query(i,j),求和a[i]+a[..]…..+a[j]

,第一个操作实现比较简单,可直接在原数组中实现,Query操作需要遍历数组,需要O(n)的时间复杂度,使用数组数组可使该操作的时间复杂度变为O(logn)

比如给你一个序列A[1….n],要求求出a[i]到a[j]的总和,我们比较容易想到的就是开一个数组C[1….n],使得
C[1]=A[1];
C[2]=A[1]+A[2];
C[3]=A[1]+A[3];
….
C[5]=A[1]+…..+A[5];
经过这种预处理之后,实现Query的时间复杂度是O(n),要想求得A[i…j]的总和,只需要计算C[j]-C[i-1]就可求得结果
树状数组跟上面的C[i]数组的表示相似,但C[i…j]并不表示A[i]…A[j]的总和,在讲C[i]的含义之前先来看下一个叫lowbitIu)的东西
i=1;lowbit(i)=1;lowbit(1)=1&-1
i=2;lowbit(i)=2;lowbit(2)=2&-2
i=3;lowbit(i)=1;
i=4;lowbit(i)=4;
i=5;lowbit(i)=1;
i=n;lowbit(i)=i&-i;
为什么?看一下1…5的二进制
1:0001,从右往左第一个1代表1
2:0010,从右往左第一个1代表2
3:0011,…
4:0100,从右往左第一个1代表4
5:0101,…
lowbit(i)代表十进制i的二进制表示中,从右往左第一个1所在位置所代表的二级制数
然后是C[i]的定义:
C[1]:以A[1]为结尾,从A[i]开始往前数(包含A[1]在内)的1(lowbit(1))个数的和,即C[1]=A[1]
C[2]:以A[2为结尾,从A[2]开始往前数(包含A[2]在内)的2(lowbit(2))个数的和,即C[2]=A[1]+A[2]
C[3]:以A[3]为结尾,从A[3]开始往前数1(lowbit(3))个数的和,即C[3]=A[2]+A[3]
C[4]:以A[4]为结尾,从A[4]开始往前数4(lowbit(4))个数的和,即C[4]=A[1]+A[2]+A[3]+A[4]
C[5]:已A[5]为结尾,从A[5]开始往前数1(lowbit(5))个数的和,即C[5]=A[5]+A[4]
C[i]:以A[i]为结尾,从A[i]开始往前数lowbit(i)个数的和

![enter description here][1]

有了以上概念,下面是数组数组的简单应用,使用create()函数根据原数组que[]创建数组数组c,使用lowbit()函数求得k,使用change()函数更新数组数组,使用sum()数组求得A[1]+…..+A[i]的总和(通过C数组来求)

实例解释HDU1166:
Problem Description
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:”你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:”我知错了。。。”但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.

Input
第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令

Output
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。

代码

/*树状数组模板题*/#include<iostream>#include<cstdio>#include<cstring>#define MAXN 50005using namespace std;int que[MAXN], c[MAXN];char chr[10];int lowbit(int x){    return x&(-x);//用来计算a^k中的k}//实现每个树状数组元素所分管的元素之和的计算void create(int n){    int i, k, j;    for (i = 1; i <= n; i++)    {        k = lowbit(i);        for (j = 0; j<k; j++)//含自身向前推k个        {            c[i] = c[i] + que[i - j];        }    }}//求数组的和的算法如下int sum(int n){    int s = 0;    while (n>0)    {        s += c[n];        n = n - lowbit(n);    }    return s;}//修改操作,往上修改void change(int i, int n, int x){    while (i <= n)    {        c[i] = c[i] + x;        i = i + lowbit(i);    }}int main(){    int t, cas = 1, i, j, n, x, s, a, b;    cin >> t;    while (t--)    {        memset(c, 0, sizeof(c));        cin >> n;        for (i = 1; i <= n; i++)            scanf("%d", &que[i]);        create(n);        cout << "Case " << cas++ << ":" << endl;        while (1)        {            scanf("%s", chr);            if (!strcmp(chr, "Add"))            {                scanf("%d%d", &j, &x);                change(j, n, x);            }            else if (!strcmp(chr, "Sub"))            {                scanf("%d%d", &j, &x);                change(j, n, -x);            }            else if (!strcmp(chr, "Query"))            {                scanf("%d%d", &a, &b);                s = sum(b) - sum(a - 1);                printf("%d\n", s);            }            else                break;        }    }    return 0;}

参考:

http://www.cnblogs.com/fancy-itlife/p/4312170.html

原创粉丝点击