数据结构——树状数组篇

原地址:My CSDN Blog  那边还未通过审核,先看这边的吧……

线段树是一个很好的维护区间关系的这样的一个数据结构,但是,很多时候我们可以用更小空间、更快速度(更大尺寸呢、,全景天窗,五菱宏光?)的数据结构来维护一个前缀关系。

???

上面的这张图是表示的一个有8个叶子节点的线段树,接下去,我们给它进行一个变形:

???

然后呢,我们把2、4、6、8、……这样的元素推到最顶端的空上边去,想让2表示1~2这段区间,让4表示1~4这段区间,让6表示5~6这段区间,让8表示1~8这段区间。

然后,这张图就变成了这样:

???

那么,我们知道了2、4、6、8可以表示的区间,诶~那其他的区间(前缀关系)呢!古人(比我老的都是古人!)想到了一种很巧妙的解决的方法,我们来举例说明:
我们想求1~1的时候,是不是就是A[1];

想求1~3的时候,是不是A[3]和A[2]两个这样的关系式,因为A[3]只表示3~3这个区间,如图;

想求1~5的时候,我们就要用A[5]、A[4]这两个关系式了;

求1~6的时候,要用A[6]、A[4]这两个式子了;

1~7呢,是A[7]、A[6]、A[4]这三个关系的统筹了。

从中,我们能否找到什么规律?

首先,我们知道树状数组仍然是跟线段树一样是一棵二叉搜索树(BIT),性质仍然没有改变。那么,我们不妨溯本求源从二进制的角度来思考问题。

我们将这8个数都转换成二进制来看,

1 = (0001)—— Q[1] = A[1];

2 = (0010)—— Q[2] = A[2];

3 = (0011)—— Q[3] = A[3] + A[2];

4 = (0100)—— Q[4] = A[4];

5 = (0101)—— Q[5] = A[5] + A[4];

6 = (0110)—— Q[6] = A[6] + A[4];

7 = (0111)—— Q[7] = A[7] + A[6] + A[4];

8 = (1000)—— Q[8] = A[8];

从中我们是不是可以发现,我们想求1~x的一个前缀的关系,是不是就可以把x看成二进制再进行求解:看到7 = (0111)是不是可以把7看成Q[7] = Q[ (0111) ] = A[ (0111) ] + A[ (0110) ] + A[ (0100) ]这样的一个关系,我们不难发现,对于7是不是每次去除的都是其二进制上的最后一位1,直到为0停止运算,譬如(0111)->(0110)我们去除了最末尾的1,(0110)->(0100)也是去除了最末尾的那个1。

Problem:怎么去除某数最末尾的那个1?
  Q1:从0开始跑一遍for(i, 0~31)循环判断到某一位"(x>>i) & 1"是不是为1?——好浪费时间的说~(留给……的时间不多了!)(脱口而出!

Q2:new algorithm?新的算法?不是新的哦~是一个我们平时没有关注到的一个小玩意!

方法一:

如何消除二进制中最后一个位1?利用n &= (n - 1)就可以做到。(手动模拟下下哦,31为二进制好长的说呢QAQ)(雾

方法二:

这时候就要回到C语言的课本知识了,我们知道负数的补码是怎样计算的呢?

举个例子:6 = (0110),那么(-6)=(1001 + 1)=(1010)

也就是先对所有位置取反,然后再"+1"这样的一个过程。那么,好巧喔,是不是发现“x&(-x)”就是我们所要删除的最后一位的1的对应的值?

给个好听的名字吧,就叫lowbit()吧!
这里还有一道还不错的题!lowbit()的「藏起来

#define lowbit(x) ( x&(-x) )

我习惯把它放在头文件里边,因为真的很常用的!

到这里,我们是不是知道了大致上该怎样去查找,那么,更新哩,一开始的树状数组是不是空的?所以,我们需要去更新它!

看到图片,再手动模拟一下给大家看看,我们要更新以下的序号,那么哪几位是需要改变的呢?(我们把图放下来继续看着图)

1(0001)—— A[1]、A[2]、A[4]、A[8];

2(0010)—— A[2]、A[4]、A[8];

3(0011)—— A[3]、A[4]、A[8];

4(0100)—— A[4]、A[8];

5(0101)—— A[5]、A[6]、A[8];

6(0110)—— A[6]、A[8];

7(0111)—— A[7]、A[8];

8(1000)—— A[8];

在这里,我们不难发现,更新的时候与查询相反,我们是去给最低位的1上加上1,然后不断的向前进位,就可以把对应的关系我们存进去。

这里给出一个求前缀和的模板(My Code):

#define lowbit(x) ( x&(-x) )
int A[maxN], N, M;    //maxN是根据题目的呢
inline void add(int x, int v)    //第x位,给它加上v的权
{
    if(!x) return;    //没有这个有可能会死循环哦,牢记!!!
    while(x <= N)
    {
        A[x] += v;
        x += lowbit(x);
    }
}
inline int query(int i)    //查询1~i的和
{
    int ans = 0;
    while(i)
    {
        ans += A[i];
        i -= lowbit(i);
    }
    return ans;
}

对了,就像Code里面讲的那样,我们更新的时候,如果没有:

if(!x) return;

是有可能会死循环的(有些地方会改变0处的值,我们得想办法去处理它的!)。

为什么会死循环!?是因为lowbit(x)也是0呀。然后就永远永远的加不到N了(雾

好啦。树状数组的基本操作就讲到这里叭,自我感觉良好~(膨胀ing???)

???

真的没有彩蛋了,别往下看了。

没彩蛋了……

没彩蛋了。

没了哦。。

原文地址:https://www.cnblogs.com/WuliWuliiii/p/11235670.html

时间: 2024-08-17 10:37:31

数据结构——树状数组篇的相关文章

数据结构——树状数组

我们今天来讲一个应用比较广泛的数据结构——树状数组 它可以在O(nlogn)的复杂度下进行单点修改区间查询,下面我会分成三个模块对树状数组进行详细的解说,分别是树状数组基本操作.树状数组区间修改单点查询的实现.树状数组查询最值的实现 一. 树状数组一般分为三种操作,初始化.修改.查询 在讲基本操作之前,我们先来看一张图 这张图就是树状数组的存储方式,对于没有接触过树状数组的人来说看懂上面这张图可能有些困难,上图的A数组就是我们的原数组,C数组则是我们需要维护的数组,这样存储能干什么呢,比如我们在

HDU 1556 数据结构-树状数组-改段求点

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1556 解题思路:树状数组,只要了解树状数组的原理就不用死记模板了,总之树状数组管理的就是前缀和,高度越高的的结点管理的范围越广 所以要是改点求段:更改一个点就要向上传递 所以要是改段求点:更改一个点就要向下传递 代码: #include <cstdio> #include <iostream> #include <cstring> using namespace std;

实用数据结构---树状数组(二叉索引树)

树状数组适用于动态连续和查询问题,就是给定一个区间, 查询某一段的和或者修改某一位置的值. 关于树状数组的结构请去百度百科,否则将看不懂下面内容 我们看这个题 士兵杀敌(二) 时间限制:1000 ms  |  内存限制:65535 KB 难度:5 描述 南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的. 小工是南将军手下的军师,南将军经常想知道第m号到第n号士兵的总杀敌数,请你帮助小工来回答南将军吧. 南将军的某次询问之后士兵i可能又杀敌q人,之后南将军再询问的时候,需要考虑到新

用树状数组处理逆序对[数据结构][树状数组]

逆序对 ——!x^n+y^n=z^n 可以到这里[luogu]: https://www.luogu.org/problem/show?pid=1908 题意:对于给定的一段正整数序列,逆序对就是序列中ai>aj且i<j的有序对.知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目. 假如为这些数为: 8 2 3 1 7 如果我们把数一个个加进来,用一个数组a[i]统计i出现了几次. a的初始状态: 8加进来后: 由于不存在比8大的数,说明没有产生逆序对 2加进来后: 统计比2大

ACM数据结构-树状数组

模板: int n; int tree[LEN]; int lowbit(int x){ return x&-x; } void update(int i,int d){//index,delta while(i<=n){ tree[i]+=d; i+=lowbit(i); } } int getsum(int i){ int ans=0; while(i>0){ ans+=tree[i]; i-=lowbit(i); } return ans; } 示意图: 1.Ultra-Quic

[数据结构] 树状数组 的C程序实现

int tree[100001];//树状数组,用于取区间[x,y]的数据的和 /* & 特殊运算,t&(-t)的值(十进制),就是t在2进制下,从右往左数第一个1出现的位置. 结合树状数组的特殊性质,这个值有用 */ int lowbit(int t) { return t&(-t); } /* 假设对处在数组序号x的数据进行了更改,让x位置的数据有了增量v 对树状数组进行如下修改,使相关的包含x位数据的和都增加v 根据树状数组的性质,也就是对下标为 x, x+lowbit(x)

2019.9.25 初级数据结构——树状数组

一.树状数组基础 学OI的同学都知道,位运算(对二进制的运算)比普通运算快很多.同时我们接触到了状态压缩的思想(即将0-1的很多个状态压缩成十进制的一个数,每一个二进制位表示一个状态).由于在实际做题过程当中,由于数据范围限制,我们必须采用更高效的存储.查询方法,于是树状数组应运而生. 首先我们检查传统的存储状态.对于数组的每一个下标i,其所存储的有效信息只有一个a[i](这是传统数组).而对于树状数组,我们每一位下标可以存储lowbit(i)个有效信息.这个lowbit运算一会再说.所以尽管树

C++-hdu1166-敌兵布阵[数据结构][树状数组]

单点修改+区间查询=树状数组 空间复杂度O(n) 时间复杂度O(mlogn) 1 #include <set> 2 #include <map> 3 #include <cmath> 4 #include <queue> 5 #include <vector> 6 #include <cstdio> 7 #include <cstdlib> 8 #include <cstring> 9 #include <

C++-hdu1394-Minimum Inversion Number[数据结构][树状数组]

给出0~n-1的一个排列,可以整体移动,求逆序对最小值 把数字num[i]的加入,等价于树状数组的第n-num[i]位加1 因为num[i]是第 (n-1)-num[i]+1=n-num[i]大的数字,产生逆序对,只可能在其之前已经插入了数字,此时直接区间查询即可 1 #include <set> 2 #include <map> 3 #include <cmath> 4 #include <queue> 5 #include <vector>