ACM 树形数组

树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值。

树状数组的解法和程序网上有很多,这里我想思考一下这种算法的灵魂,也就是基于什么样的契机和灵感产生了这种绝妙的想法。这是我感兴趣的方向。

这种算法,主要用于查询数组中任意两个数之间的所有元素之和,而且这个数组我们会经常修改里面任意的数。

如果抛弃修改数组这个操作,也就是原数组是不变的,只是做查询的话,我们会很容易想到一种方法。

设 原数组 a[n],我们可以构造另一个数组c[n],取任意下标i,让 c[i] = a[1] + a[2] + …… + a[i]

这样如果我们要计算 下标k和j之间的数的和(包括k和j),sum = c[j] - c[k-1]

因此只要构建了数组c[n],查询操作的时间复杂度都是 o(1) 级别的,非常快

如果加入了修改操作的话,上边的方法就不太适合了,因为如果 修改了 a[i],对应的 c[i]、c[i+1]、……、c[n],都要因此修改。这修改后的时间复杂度是 o(n) 级别的,虽然查询操作还是 o(1) ,但如果修改操作很多,这种方法显然不适合。

如果说在这种方法之上改进一下,让修改操作的时间复杂度减少,查询操作时间复杂度增加,树状数组这种算法就做到了这一点,让 修改和查询的时间复杂度都统一为o(log(n)),log(n)的好处是n越大,带来的效率优化就越高

让我们看下改进后的区别

原来:

c[1] = a[1]
c[2] = a[1] + a[2]
c[3] = a[1] + a[2] + a[3]
...
c[n] = a[1] + a[2] + a[3] + …… + a[n]

改进后:

c[1] = a[1]
c[2] = a[1] + a[2]
c[3] = a[3]
c[4] = a[1] + a[2] + a[3] + a[4]
c[5] = a[5]
c[6] = a[5] + a[6]
c[7] = a[7]
c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8]
...
c[n] = a[n – 2^k + 1] + ... + a[n] 【其中k为n二进制末尾0的个数】

从改进后的规律来看,如果我们修改了a[i],我们就不用将所有c[i]之后的元素都修改了,而是有所选取的修改。这个修改的时间复杂度是o(log(n)),可以从n – 2^k + 1这个下标公式看出来。

用一张树状图来表示的话会非常直观

查询操作的话,以计算前n个数的和为例,

sum(7) = c[7] + c[6] + c[4] = c[0x111] + c[0x110] + c[0x100]

sum(10) = c[10] + c[8] = c[0x1010] + c[0x1000]

sum(13) = c[13] + c[12] + c[8] = c[0x1101] + c[0x1100] + c[0x1000]

看到这里聪明的你应该会发现一些规律,以7的二进制 0x111 为例,从右到左逐渐去掉1,  0x110,0x100 也就是 6 和 4,因此得到 c[7] + c[6] + c[4]

用这种方法就能求出前n个数的和,然后如果我们要计算 下标k和j之间的数的和(包括k和j),sum = c[j] - c[k-1]

程序如下:

#include <stdio.h>
//#include <windows.h>
#include <string.h>

#define MAX 1000001

int c[MAX];

// 用此方法可以计算二进制数从右到左数第一个1出现的数
// 例子:
// 1————1
// 10————10
// 110100————100
// 10111————1
// 10000————10000
int lowbit(int t)
{
    return t&(-t);
    //return n&(n^(n-1));
}

// 修改数组中某个数,delta是增量
void modify(int n, int delta)
{
    while(n <= MAX)
    {
        int d;
        c[n] += delta;
        n += lowbit(n);
    }
}

// 求前n个数的和
int sum(int n)
{
    int result = 0;
    while(n != 0)
    {
        result += c[n];
        n -= lowbit(n);
    }
    return result;
}

int main()
{
    int N = 0, M = 0, i = 1;
    scanf("%d %d", &N, &M);
    memset(c, 0, sizeof(c));
    while(N--)
    {
        int temp;
        scanf("%d", &temp);
        modify(i++, temp);
    }
    while(M--)
    {
        char s[10];
        int I = 0, A = 0, num;
        scanf("%s %d %d", &s, &I, &A);
        if(strcmp(s, "QUERY") == 0)
        {
            num = sum(A) - sum(I-1);
            printf("%d\n", num);
        }
        else if(strcmp(s, "ADD") == 0)
        {
            modify(I, A);
        }
    }
    //system("pause");
}

总结:在无修改操作的情况下,我们用c[n]来表示数组a中前n个数的和sum(n),在有修改操作的情况下,我们还是用数组c中的数来表示sum(n),不同的是,这里的会用到多个数如 c[i]、c[j]、c[k]……,而如何通过n来找到i,j,k……这些相关数和构造出数组c,就是树形数组这种算法的关键所在——将数n分解为2的次方数,如2、4、8、16。

时间: 2024-08-24 19:35:06

ACM 树形数组的相关文章

树形数组——摘星星伪题解

树形数组. 题目:摘星星 描述:宇航员经常检测星图,在星图上,星星由点表示而且每颗星星都有笛卡尔坐标.星星的等级表示左下方星星的数量.宇航员想知道星星等级的分布. 例如,如上面图形所示,第5号星等级是3 (它由三个标记为1,2和4的星组成).标记着2和4的星星的等级是1,在此地图上,0等级的星星只有一个,1等级的有两个,2等级的有一个,3等级的有一个. 你设计一个程序,在给定地图上计算出每个等级星星的数量. 输入: 输入文件“INPUT.TXT”的第一行包含N个星星(1<=N<=60000),

【SGU 180】Inversions —— 归并排序或树形数组计算逆序对

原题链接 180. Inversions time limit per test: 0.25 sec. memory limit per test: 4096 KB input: standard output: standard There are N integers (1<=N<=65537) A1, A2,.. AN (0<=Ai<=10^9). You need to find amount of such pairs (i, j) that 1<=i<j&l

树形数组 学习之外总能发现别人更好的

 <html> <HEAD></HEAD> <BODY> <textarea rows="50" cols="50">  /***************** http://www.anycodes.cn/zh/  [[树状数组]线段数]  高效:log(n)  操作:位操作  思想:二分法  百度百科之外还有以下博客 http://dongxicheng.org/structure/binary_inde

PHP递归生成树形数组

数据表结构 id   name  pid       private function tree($data,$p_id=0){ foreach($data as $row){ if($row['pid']==$p_id){ $tmp = $this->tree($data,$row['id']); if($tmp){ $row['child']=$tmp; }else{ $row['leaf'] = true; } $tree[]=$row; } } Return $tree; }

树形数组暴力

对不起,常数小就是可以为所欲为的. 题目 // luogu-judger-enable-o2 #include<cstdio> #include<algorithm> #include<iostream> using namespace std; struct node { int tree[100100]; int num; int lowbit(int x) { return x&(-x); } void updata(int x,int i) { whil

敌兵布阵_区间求和问题_线段树 or 树状数组

敌兵布阵 TimeLimit: 2000/1000 MS (Java/Others)  MemoryLimit: 65536/32768 K (Java/Others) 64-bit integer IO format:%I64d Problem Description C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况.由于采取了某种先进的监测手段,所

一些关于数组查找的题目

//ACM 1 数组 #include <stdio.h> #include "sort.h" int arrDump(int * a, int n) { int i = 0; printf("Array:["); for (i=0; i<n; i++) { printf(" %d ", a[i]); } printf("]\n"); return 0; } /* 删除有序数组中重复元素 时间 O(n) 空间

php 实现树形结构

<?phpclass Tree{ private $OriginalList; public $pk;//主键字段名 public $parentKey;//上级id字段名 public $childrenKey;//用来存储子分类的数组key名 function __construct($pk="id",$parentKey="pid",$childrenKey="children"){ if(!empty($pk) &&

php通用的树型类创建无限级树型菜单

生成树型结构所需要的2维数组,var $arr = array()数组格式如下: array( 1 => array('id'=>'1','parentID'=>0,'name'=>'一级栏目一'), 2 => array('id'=>'2','parentID'=>0,'name'=>'一级栏目二'), 3 => array('id'=>'3','parentID'=>1,'name'=>'二级栏目一'), 4 => arra