树状数组

题目链接


树状数组推荐看训练指南的说明,90%能看懂。不能看懂再往下看。


求a1,a2...an的和,下面看 CS 97SI: Introduction to Competitive Programming Contests  的PPT里的图片。

思想直接看原文:


Each internal node stores the sum of values of its children
– e.g., Red node stores item[5] + item[6]


Main idea: choose the minimal set of nodes whose sum gives the desired value

I We will see that
– at most 1 node is chosen at each level so that the total number of nodes we look at is log 2 n
– and this can be done in O(logn) time



计算方法


//版本2

树状数组是对数组元素修改和查询都是O(logn)的结构,主要用于查询任意两位之间的所有元素之和,可参考百度


最基本的是在有修改元素的情况下求区间内元素和

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

lowbit(i):将i转化成二进制数之后,只保留最低位的1及其后面的0,截断前面的内容,然后再转成10进制数

X^:X取反(符号我这里定的,不是什么官方符号)

lowbit(x)实际上是提取x从左往右数的最后一个1。
设x为a1b,a1b中的1位最后一个1,a和b都表示一串01串(当然b全为0或者不存在),如13=1101则a=110,b不存在。
对一个数x取负相当于该数的二进制取反再加1,所以-x = (a^- 1^ b^) = (a^ 0 1...1) +1 = (a^ 1 0...0)。
则 x & (-x) = (a 1 00...) & (a^ 1 00...) = (0...0  1 0...0)。结果就是保留最后个1和后面的0的十进制答案。


10进制值二进制值lowbit()值
2102
3111
41004
51011

那么 i + lowbit( i ) 可以求出父节点的下标

把子树向右对称翻得出一些空白结点,树变成完全二叉树,看图可知父节点下标和空白结点下标相同



i            lowbit(i)    i+lowbit(i) 
224
314
448
516

像 i == 5时,父节点下标是6,表示c[5]在c[6]管辖下,c[6]还管了个A[6],c[6] = c[5] + a[6]

void add(int i,int num)
{
    while(i <= n)
    {
        c[i] += num;
        i += lowbit(i);
    }
}
给a[ i ]加值,则要把c[ i ] 和 管理c[ i ]的那些 c[ x ] 也加上相同的值,如给1 号元素加1,则要把c[1],c[2],c[4],c[8]都加1。add()代码中 i += lowbit(I)就是父节点下标

那么减值就是参数为负数就行

int getSum(int i)
{
    int sum = 0;
    while(i > 0)
    {
        sum += c[i];
        i -= lowbit(i);
    }
    return sum;
}
 i - lowbit( i ) 求出跟 另一个c[ x ] ,c[ x ] 管理了 小于 i 的 a [ i ] 中,c[ i ] 所没有管理到的 a[ i ],那么把c [ i ] 和 c [ x ]加起来就是A[1]...A[ i ]的和

i12345678
i - lowbit(i)00204460
如求a[1] ... a[6],那么求c[6] + c[ 6-lowbit(6) ]即 c[6] + c[4]即可


还不过瘾就看看http://www.hawstein.com/posts/binary-indexed-trees.html吧,是Topcoder文章的翻译


这种直接插入求和,修改值的函数( 这里的add() )的循环是从下往上,而求和函数是从上往下,所以也被归类为 向上查询,向下求和的类型


下面来道向下查询,向上求和的题目

HDU 1556 Color the ball

题目很好理解,代码实现不需要a数组。但add()一改就是改一串相关联的,那么要改区间[a,b]元素的值,可以这样:把[1,b]元素值+1,再把[1,a-1]值-1,抵消后就是把[a,b]的元素值+1,因为改的是小于等于 i 的部分值,所以是向下查询

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#define FOR(i,n) for(int i=0;i<(n);i++)
#define ll __int64
#define NMAX 100001
using namespace std;
int c[NMAX],n;
int lowbit(int t)
{
    return t&(-t);
}
void add(int i,int num)
{
    while(i > 0)
    {
        c[i] += num;
        i -= lowbit(i);
    }
}
int getSum(int i)
{
    int sum = 0;
    while(i <= n)
    {
        sum += c[i];
        i += lowbit(i);
    }
    return sum;
}
int main()
{
  //  freopen("in.txt","r",stdin);
    int a,b;
    while(scanf("%d",&n)!=EOF,n)
    {
        memset(c,0,sizeof(c));
        for(int i=0;i<n;i++)
        {
            scanf("%d%d",&a,&b);
            add(b,1);
            add(a-1,-1);
        }
        printf("%d", getSum(1));
        for(int i=2;i<=n;i++)
        {
             printf(" %d",getSum(i));
        }
        printf("\n");
    }
    return 0;
}

HDU 1166 敌兵布阵  此题还有线段树解法

#include <cstdio>
#include <cstring>
#define INF 0x7FFFFFFF
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define CLR( a , x ) memset ( a , x , sizeof (a) )
#define RE freopen("1.in","r",stdin)
#define LL(i) (i<<1)
#define RR(i) (i<<1|1)
#define MAX 50010
int ans;
int a[MAX];

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

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

void add(int i,int num)
{
    while(i <= MAX)
    {
        a[i] += num;
        i += lowbit(i);
    }
}

int main()
{
    RE;
    int n,m,l,r,t,x;
    char cmd[10];
    scanf("%d",&t);
    FOR(tt,1,t)
    {
        CLR(a,0);
        printf("Case %d:\n",tt);
        scanf("%d",&n);
        FOR(i,1,n)
        {
            scanf("%d",&x);
            add(i,x);
        }
        getchar();
        while(scanf("%s",cmd),cmd[0]!='E')
        {
            switch(cmd[0])
            {
                case 'Q':
                        scanf("%d%d",&l,&r);
                        ans = sum(r) - sum(l-1);
                        printf("%d\n",ans);
                        break;
                case 'A':
                        scanf("%d%d",&l,&r);
                        add(l,r);
                        break;
                case 'S':
                        scanf("%d%d",&l,&r);
                        add(l,-r);
                        break;
            }
        }
    }
	return 0;
}






  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值