树状数组简单入门

树状数组简单入门

树状数组是一个查询和修改复杂度都为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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值