树状数组简单入门

来源:互联网 发布:js将字符串转化成数字 编辑:程序博客网 时间:2024/05/22 10:29

树状数组简单入门

树状数组是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和。但是每次只能修改一个元素的值,不如线段树的应用范围广,但是写起来比线段树简单很多,空间复杂度也会低一点。可以用来解一些像求逆序数的题。

网上用的很多的一张树状数组图
上面这张图就表示了树状数组与原数组的关系
c数组完整的保存了a的所有的信息,我们可以通过对c数组的操作来查询a数组的区间和还有修改a数组的单个元素的值。

c是怎么表示a的呢?

c[i] = a[i – 2^k + 1] + … + a[i],k为t在二进制下末尾0的个数
按照上面的规则c[i]也可以认为是由a[i]向前加和lowbit(i)个a数组值
lowbit(i)可以用下面的代码求出:

int lowbit(int i){    return i&(-i);}

获得了c数组后要怎么求区间和呢?

我们可以利用c数组求出1~n区间内的区间和求解方法如下:

int sum(int i){    int sum = 0;    while(i>0)    {        sum += c[i];        i =i - lowbit(i);    }    return sum;}

先令待求区间和为0,之后在依次加上c[i],c[i-lowbit(i)],c[i-lowbit(i)-lowbit(i-lowbit(i))]….显然最多加log2(i)次,所以求和的复杂度为O(n)。
为了清楚表示这个过程,用一张表格模拟1~8的求和过程
求和
对任意区间[x,y]区间和可以简单的由sum[y]-sum[x-1]求得。

如何更新点的值呢?

改变a[i],必须要将包含a[i]的所有c[x]的值都改变掉,如何获取包含a[i]的c[x]呢?
从图上可以发现对任意c[i]他的父亲节点为c[lowbit(i)+i],显然我们第一个要改的值就是c[i],改变c[i]后逐步向上修改
(以a[i]加一个值为例)代码如下:

void add(int i,int value){    while(i<=N)    {        c[i] += value;        i = i+ lowbit(i);    }}

解决了上面的三个问题就可以用树状数组解题啦
下面给一道树状数组的题:hdoj1166

敌兵布阵

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以内。

ac码

#include <iostream>#include<cstdio>#include<cstring>using namespace std;const int MAX = 50005;int N;int c[MAX];int lowbit(int i){    return i&(-i);}void add(int i,int value){    while(i<=N)    {        c[i] += value;        i = i+ lowbit(i);    }}int sum(int i){    int sum = 0;    while(i>0)    {        sum += c[i];        i =i - lowbit(i);    }    return sum;}int main(){    int T;    int Case = 0;    scanf("%d",&T);    while(T--)    {        printf("Case %d:\n",++Case);        memset(c,0,sizeof(c));        cin >> N;        for(int i = 1; i <= N; i++)        {            int d;            scanf("%d",&d);            add(i,d);        }        char command[15];        while(scanf("%s",command)&&command[0]!='E')        {            if(command[0] == 'Q')            {                int x,y;                scanf("%d%d",&x,&y);                printf("%d\n",sum(y)-sum(x-1));            }            if(command[0] == 'A')            {                int i,value;                scanf("%d%d",&i,&value);                add(i,value);            }            if(command[0] == 'S')            {                int i,value;                scanf("%d%d",&i,&value);                add(i,-value);            }        }    }    return 0;}
0 0
原创粉丝点击