理解树状数组

树状数组又名二分索引术,主要包含两种基本操作

1.Update(int i,int val)更新节点及其所有父节点及祖先节点的值,表示对第i点的值增加val。时间复杂度O(logn)

2.Sum(int i)表示对前i个点进行求和操作.时间复杂度O(logn),n表示节点总数,logn即log2n。

树状数组是通过数组来实现的一种轻量级的数据结构,性价比较高。

主要实现

定义数组C[i],A[i]。C[i]=A[i-2^k+1]+A[i-2^k+2]+......+A[i],这里k表示i在二进制表示下末尾数字0的个数。

一种较为直观的判断方法可以直接找出i的因子中2的最高次方即k同时2^k就是A[i]的个数,例如8是1000,因子有2的3次方,所以c[8]=A[1]+A[2]+......+A[8]

在这里i=8,一定是从A[x]开始一直加到A[8],8=2^3,因此判断有8个数相加,一直加到A[8]为止,所以一定是从A[1]开始加的。再如6=3*2^1,只有两个数相加,一定从A[5]开始加

见下图

根据刚才对C[i]的定义可知,当i为奇数时C[i]=A[i],当i为偶数时需要进行计算以求得i-2^k+1

C1 = A1
C2 = C1 + A2 = A1 + A2
C3 = A3
C4 = C2 + C3 + A4 = A1 + A2 + A3 + A4
C5 = A5
C6 = C5 + A6 = A5 + A6
C7 = A7
C8 = C4 + C6 + C7 + A8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

可以直观的看到若改变某个A[i]的值,A[i]的父节点及祖先节点的值也要相应的改变

在函数实现之前先来介绍一种运算,以函数的方式来实现

int Lowbit(int x)

{

  return x&(-x);

}

这个函数求的就是2^k。

具体运算是通过补码方式来实现的,不懂补码概念的自行去脑补先

以6为例,6的原、反、补码都是0110,-6的原码是1110(假定第一位都是符号位),反码是1001,补码是1010.两个补码进行&运算得到的结果是2

再来看看函数的具体实现

1.更新操作Update(int i,int val)

假设要实现A[i]=A[i]+val的操作,这里我们不直接对A[i]直接操作,而是改变C[i]及C[i]的所有祖先节点的值

例如改变C[1]的值需要改变C[2]、C[4]、C[8]的值

void Update(int i,int val)
{
  while(i<=n)
  {
    C[i]+=val;
    i+=Lowbit(i);
  }
}

提供一种非递归的写法

void add(int i,int val)
{
  for(i;i<=n; i+=lowbit(i))
  {
    C[i] += val;
  }
}

2.求和操作Sum(int i)

求A1~Ai的和

sum(i) = sum{ A[j] | 1 <= j <= i } = A[1] + A[2] + … + A[i]
= A[1] + A[2] + A[i-2^k] + A[i-2^k+1] + … + A[i]
= A[1] + A[2] + A[i-2^k] + C[i]
= sum(i - 2^k) + C[i]
= sum( i – lowbit(i) ) + C[i]

int Sum(int i)
{
  int sum=0;
  while(i>0)
  {
    sum+=C[i];
    i-=Lowbit(i);
  }
  return sum;
}

同样提供一种非递归写法

int Sum(int i)
{
  int sum=0;
  for(i; i ; i -= lowbit(i))
  { 
    sum+=C[i];
  }
  return sum;
}

原博请见:http://www.open-open.com/lib/view/open1450603621287.html

题目推荐的话后续再更新吧,我也是刚入门>_<

时间: 2024-08-27 00:19:42

理解树状数组的相关文章

理解树状数组与POJ 2352

学习自:链接以及百度百科 以及:https://www.bilibili.com/video/av18735440?from=search&seid=363548948825132979 理解树状数组 概念 假设数组a[1..n],那么查询a[1]+...+a[n]的时间是log级别的,而且是一个在线的数据结构,支持随时修改某个元素的值,复杂度也为log级别. 观察这棵树,容易发现: C1 = A1 C2 = A1 + A2 C3 = A3 C4 = A1 + A2 + A3 + A4 C5 =

深入理解树状数组

树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值:经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询). 百度上给出了令人难以理解的概念,其实这个东西我也是琢磨了一天,参考了大量博客的笔记才搞清楚了大致思路和原理,说说心得吧! 假设数组a[1..n],那么

小白初理解树状数组

ACM的在线测试里经常涉及到大量数据的的修改,求和等操作,这里介绍一种方法——树状数组. 树状数组,是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值:经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值.可以用一张图来弄懂什么是数组数组. 原数组A[n],树状数组C[n]; 如果n为奇数:Cn=An; 如果n为偶数:Cn = A(n – 2^k + 1) + ... + An,k为n的二进制数

Mobile phones_二维树状数组

[题意]给你一个矩阵(初始化为0)和一些操作,1 x y a表示在arr[x][y]加上a,2 l b r t 表示求左上角为(l,b),右下角为(r,t)的矩阵的和. [思路]帮助更好理解树状数组. #include<iostream> #include<stdio.h> #include<string.h> using namespace std; const int N=1050; int c[N][N]; int s; int lowbit(int x) { r

SPOJ DQUERY D-query 离线+树状数组

本来是想找个主席树的题目来练一下的,这个题目虽说可以用主席树做,但是用这个方法感觉更加叼炸天 第一次做这种离线方法,所谓离线,就在把所有询问先存贮起来,预处理之后再一个一个操作 像这个题目,每个操作要求区间不同元素的个数,我盲目去查的话,某个元素在之前如果出现了,我把他算在当前区间也不好,算在之前的区间也不好,都会出错. 一个好的方法就是把区间排好序,针对某个区间在树状数组上更新以及查询相应值,这样能准确查出结果,但又不影响之后的查询 具体来说,先把区间按右端点进行排序(我一开始按左端点排,想错

P3368 【模板】树状数组 2 单点查询与区间修改

题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数数加上x 2.求出某一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别表示该数列数字的个数和操作的总个数. 第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值. 接下来M行每行包含2或4个整数,表示一个操作,具体如下: 操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k 操作2: 格式:2 x 含义:输出第x个数的值 输出格式: 输出包含若干行整数,即为所有操作2的结

POJ 2309 BST(树状数组Lowbit)

题意是给你一个满二叉树,给一个数字,求以这个数为根的树中最大值和最小值. 理解树状数组中的lowbit的用法. 说这个之前我先说个叫lowbit的东西,lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1,比如10的二进制是1010,则lowbit(k)=lowbit(1010)=0010(2进制),介于这个lowbit在下面会经常用到,这里给一个非常方便的实现方式,比较普遍的方法lowbit(k)=k&-k,这是位运算,我们知道一个数加一个负号是把这个数的二进制取反+1,如-1

树状数组知识点详解

树状数组 树状数组是一种数据结构,它的作用就是优化查询和修改的操作.试想,我们假如在做一道题的时候使用裸的一维数组来存储数据,那每次区间修改需要O(1)的时间,但查询却需要O(n)的时间,针对于某些题目,数据量奇大无比,必然会TLE.所以我们使用树状数组来优化这两个操作,使得修改和查询均可以在O(logn)的时间内完成,提升效率. (这是百度百科上树状数组的图) 可以直观地看出树状数组是个什么模式,是的,这就是一棵树,而这棵树上每个节点存储的数据就是它所有儿子节点的数据和.所以我们就可以在树上做

POJ2309BST【树状数组的理解】

大意: 对于这个树 告诉你一个节点问这个节点下的最小值和最大值 分析: 这个题考查对于树状数组的理解,   每个节点的前一个节点都是依次向前的 比如 10--8--4--2--1 后一个节点都是一次往后的比如10--12--16…… 那么我们观察发现每个节点的最小值都是他爹+1,最大值都是他妈-1 代码: 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace st