『数据结构』树状数组

树状数组的问题模型:

现在有一个这样的问题:

有一个数组\(a\),下标从\(0\)到\(n-1\),现在你要进行\(w\)次修改,\(q\)次查询。

修改是修改数组中某一个元素的值;

查询是查询数组中任意一个区间的和,\(w+q<500000\)。

这个问题很普遍,首先分析下朴素做法的时间复杂度,

修改是\(O(1)\)的时间复杂度,

而查询就要\(O(n^2)\)的复杂度,总体时间复杂度为\(O(q*q*n*n)\);

你或许会想到用前缀和来优化这个查询,

我们再来分析下,查询的话是\(O(1)\)的复杂度,

但是修改的时候修改一个点,那么在之后的所有前缀和都要更新,

所以修改的时间复杂度是\(O(n^2)\),总体时间复杂度还是\(O(q*q*n*n)\)。

可以发现,两种做法中,要么查询是\(O(1)\),修改是\(O(n^2)\);

要么修改是\(O(1)\),查询是\(O(n^2)\)。

有没有一种做法可以降低时间复杂度呢?树状数组。

我们先来了解下\(lowbit\)这个函数,

你也先不要问这个函数到底在树状数组中有什么用;

\(lowbit\)这个函数的功能就是求某一个数的二进制表示中最低的一位1,

\(for\) \(example\),\(x=6\),它的二进制为\(110\),

那么\(lowbit(x)\)就返回\(2\),因为最后一位\(1\)表示\(2\)。

我们怎么求\(lowbit\)呢?

求负数的补码的简便方法:

先把这个数的二进制写出来,然后从右向左找到第一个\(1\),这个\(1\)不要动和这个\(1\)右边的二进制不变,左边的二进制依次取反,这样就求出的一个数的补码,说这个方法主要是让我们理解一个负数的补码在二进制上的特征,然后我们把这个负数对应的正数与该负数与运算一下,由于这个\(1\)的左边的二进制与正数的原码对应的部分是相反的,所以相与一定都为\(0\),由于这个\(1\)和这个\(1\)右边的二进制都是不变的,因此,相与后还是原来的样子,

所以,这个得出的结果就是\(lowbit(x)\)的结果。

lowbit函数:

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

二进制的视角:一个数\(n\),假设\(n=6\),它的二进制为\(110\),我们把它表示成累加的形式\(110=100+10\),这样是可以的,那么我们要求前\(6(110)\)项的和可以这样求:

\(∑i=16=(a[1]+a[2]+a[3]+a[4])+(a[5]+a[6])\)

注意括号中的元素个数,

是不是\(4(100)\)个加\(2(10)\)个,

和\(110=100+10\)是不是很像,

\(10\)就是\(lowbit(110)\)的结果,\(100\)是\(lowbit(100)\)的结果。

求和的时候我们总是把\(∑ni=1\)和\(∑i=1n\)拆分成这样的几段区间和来计算,

区间的起点和长度就是根据\(n\)的二进制来的,

二进制怎么拆的,你就怎么拆分,而拆分二进制就要用到上面说的\(lowbit\)函数了。

这里也可以顺理成章得给出\(c\)数组的表示了。

\(c[i]\)表示从第i个元素向前数\(lowbit(i)\)个元素,这一段的和,这就是上面说的区间和,只不过这个区间是靠右端点的;你可能又想问,不是说区间是靠右端点的吗,是后缀和啊,那中间的这些区间怎么定义?其实递归定义就好了,比如说:

\(∑6i=1=(a[1]+a[2]+a[3]+a[4]+(a[5]+a[6])=\)

\(∑6i=1=(a[1]+a[2]+a[3]+a[4])+c[6]\)

\(∑i=16=(a[1]+a[2]+a[3]+a[4])+(a[5]+a[6])=\)

\(∑i=16=(a[1]+a[2]+a[3]+a[4])+c[6];\)

你可以把\(c[6]\)去掉,就变成了

\(∑4i=1=(a[1]+a[2]+a[3]+a[4])\)

\(∑i=14=(a[1]+a[2]+a[3]+a[4])\)

这个区间就靠右端点了,

\(∑4i=1=c[4]=c[6-lowbit(6)]\)

\(∑i=14=c[4]=c[6-lowbit(6)]\)。

设计一种数据结构,需要的操作无非就是更改和查询,

这里只讨论查询和修改操作具体是怎么实现的;

查询

这里说的查询是查询任一区间的和,由于区间和具有可加减性,所以转化为求前缀和;

查询前缀和就是把大区间分成几段长度不等的小区间,然后再求和。

区间的个数为\(O(logn*logn)\),

所以查询的时间复杂度为\(O(logn*logn)\)。

修改

修改某一位置上的元素的时间复杂度为\(O(1)\),

但是要更新\(c\)数组,不然查询的时间复杂度就会变高。

更新的方法就要提一下树状数组的性质了和树状数组那张经典的图片了。

图片中已经把\(c\)数组的后缀和这个含义已经表达得很清楚了。

这个时候你再把查询操作对应到这张图上,

然后根据二进制来操作,

就可以很直白地理解上面所说的查询操作了!

树状数组的代码实现

对某个元素进行加法操作:

void update(int x)
{
    while(x<=n)
    {
        c[i]+=x;
        x+=lowbit(x);
    }
}

查询前缀和:

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

洛谷树状数组题:

【模板】树状数组 1:p3374

【模板】树状数组 2:p3368

中位数:p1168

逆序对:p1908

虔诚的墓主人:p2154

无尽的生命:p2448

推销员:p2672

上帝造题的七分钟:p4514

原文地址:https://www.cnblogs.com/shenxiaohuang/p/10162298.html

时间: 2024-07-30 10:59:27

『数据结构』树状数组的相关文章

数据结构:树状数组

关于树状数组的概述,可以看一下这篇博客:http://blog.csdn.net/int64ago/article/details/7429868 树状数组是一个可以高效地进行区间统计的数据结构,在思想上类似于线段树,比线段树节省空间,编程复杂度比线段树低,但适用范围比线段树小.主要工作也是查询和更新. 例题:POJ - 2352    (http://poj.org/problem?id=2352) 题目大意:输入n个星星坐标,坐标按y递增顺序输入,y相同按x递增顺序输入.定义一个星星的级别是

【数据结构之树状数组】从零认识树状数组

一.关于树状数组 树状数组(Binary Indexed Tree,简称BIT),是一种修改和查询复杂度都为O(logN)的数据结构.但树状数组仅支持单点修改,在查询时,树状数组也要求被查询的区间具有可区间加减的性质.不过,树状数组由于代码实现容易.占用空间小,常用于代替线段树. 二.详解树状数组 在这里,我们定义原序列为a,树状数组为c,则有: 其中,k为i的二进制表示中末尾0的个数,例如:i=3(101)时,k=0:i=8(1000)时,k=3. 定义函数lowbit(x)=2k(k为x的二

数据结构之树状数组

树状数组适合单个元素经常修改,而且还要反复求某个区间的和 树状数组的编程效率和程序运行效率都要比线段树要高(时间复杂度一样,但是梳妆数组的常数较小) 如果每次修改的不是一个数,而是一个区间就不适合用树状数组了(效率较低) 树状数组的时间复杂度总结: 建数组0(n) 更新0(logn) 局部求和0(logn) 当想要查询一个SUM(n)(求a[n]的和),可以依据如下算法即可: step1: 令sum = 0,转第二步: step2: 假如n <= 0,算法结束,返回sum值,否则sum = su

数据结构1——树状数组

一.相关定义 树状数组 获取数组中连续n个数的和 修改数组中某点的值 时间复杂度:O(logn) 小结:树状数组的强项在于对数组进行维护查询(如,修改某点的值.求某个区间的和).当然,数据规模不大的时候,对于修改某点的值是非常容易的,复杂度是O(1),但是对于求一个区间的和就要扫一遍了,复杂度是O(N),如果实时的对数组进行M次修改或求和,最坏的情况下复杂度是O(M*N),当规模增大后这是划不来的!而树状数组干同样的事复杂度却是O(M*logN). 二.算法描述 列出即将出现的知识定义: low

【数据结构】树状数组

树状数组 ta的本质是利用二进制的性质维护一组数据 最常用的操作就是求前缀和 int lowbit(int x){ return x&(-x); /*通过补码,清空高位1,只留下最后一个1*/ } void add(int x,int val){ while(x<=n){ c[x]+=val; x+=lowbit(x); } /*更新时,需要去把该节点所被管辖的结点全部更新 你比如说1101->1110 所被管辖的结点 1110->10000 被管辖的结点,是利用二进制来求的 最

数据结构(树状数组):HEOI2012 采花

[题目描述] 萧薰儿是古国的公主,平时的一大爱好是采花. 今天天气晴朗,阳光明媚,公主清晨便去了皇宫中新建的花园采花.花园足够大,容纳了n朵花,花有c种颜色(用整数1-c表示),且花是排成一排的,以便于公主采花.公主每次采花后会统计采到的花的颜色数,颜色数越多她会越高兴!同时,她有一癖好,她不允许最后自己采到的花中,某一颜色的花只有一朵.为此,公主每采一朵花,要么此前已采到此颜色的花,要么有相当正确的直觉告诉她,她必能再次采到此颜色的花.由于时间关系,公主只能走过花园连续的一段进行采花,便让女仆

浅谈树状数组

还是区间求和区间修改的问题,我们使用线段树解决以后发现编程复杂度比较大 在这里介绍一个简单的数据结构,树状数组. 树状数组的优势是编程复杂度小,常数小,时间复杂度也不错 树状数组的查询,修改,都是LOG(N)级别的 下面来分析一下上面那个图看能得出什么规律: 据图可知:c1=a1,c2=a1+a2,c3=a3,c4=a1+a2+a3+a4,c5=a5,c6=a5+a6,c7=a7,c8=a1+a2+a3+a4+a5+a6+a7+a8,c9=a9,c10=a9+a10,c11=a11.......

树状数组小结

树状数组基本概念 树状数组也是进行区间操作的常用数据结构.树状数组适用于单个元素经常修改,而且还反复求部分的区间和的情况. 对于数组a,构造一个新的数组C,使得C[i] = a[i-2^k+1] + a[i-2^k+2] + ... + a[i]; (1) i >= 1: (2) k为i在二进制表示下末尾的连续的0的个数,2^k = i&(-i),通常用lowbit(i)表示i对应的2^k,lowbit(i) = 2^k = i&(-i): 则数组C为数组a的树状数组. 数组数组的结

树状数组详解(图形学算法)

目录 一.从图形学算法说起 1.Median Filter 概述 2.r pixel-Median Filter 算法 3.一维模型 4.数据结构的设计 5.树状数组华丽登场 二.细说树状数组 1.树 or 数组? 2.结点的含义 3.求和操作 4.更新操作 5.lowbit函数O(1)实现 6.小结 三.树状数组的经典模型 1.PUIQ模型 2.IUPQ模型 3.逆序模型 4.二分模型 5.再说Median Filter 6.多维树状数组模型 四.树状数组题集整理 一.从图形学算法说起 1.M