jpa query 取数组第一个_数据结构篇之数状数组-Finwick Tree 全解析

本文深入解析树状数组的概念、实现和应用,包括单点修改区间查询、区间修改单点查询及区间最值问题。通过约8千字的详细讲解,阐述树状数组如何在O(logn)的时间复杂度内解决区间和问题,对比线段树,展示其简洁高效的特性。并提供实战例题和代码,帮助读者掌握这一强大工具。
摘要由CSDN通过智能技术生成

Intro

为什么要学习树状数组:写起来快、简单。

但是树状数组的代码其实并不直观,曾经我花了很长时间学习,理解补了放弃了,直接学了线段树,现在再回过头来认真学习了树状数组,发现emmmmm,这种数据结构真的很神奇也很优雅。

本文约8千字,阅读完可以学到:

1.树状数组的原理、实现

2.如何使用树状数组解决“单点修改区间查询“,“区间修改单点查询”,”区间修改区间查询“,”区间最值“等问题以及一些例题的代码

介绍与原理

树状数组,顾名思义是一个数组,通常用于解决区间问题,比如区间和。

给定一个数组
,长度为
,支持两种操作:

1. 将
更新为

2.求区间和

第一个操作实现起来很简单,直接改掉即可,重点在第二个操作,在暴力的情况下,复杂度为

,时间复杂度过高。

此时树状数组就可以出马,数状数组首先是一个数组,数组中的每个元素维护着一个区间的区间和。从含义上讲,它维护的区间长的像一颗二叉树。

3aaff75b9f81d785b771faa17a0372d6.png

假设数组长度

,我们开一个数状数组
,大小和
一样,请注意,数状数组解决的问题下标需要从1开始。

第一个重点来了,

和它维护的区间
有什么关系?
  1. 这和
    的二进制表示有关,比如
    的维护区间是
    的维护区间是
    的维护区间则是
  2. 每个
    维护的区间右端点一定是

那么只需要知道每个

维护的区间长度,我们就可以找到左端点了,这个区间长度和
的二进制表示有关。
  1. 不那么显然:
    维护的区间长度是
    二进制表示的最右位的1对应的值

比如,

对应的最右边的1对应的值是
,因此它维护的区间长度是4,区间为

计算一个数字

在二进制下最右1的权值操作叫计算lowbit(i),它可以简单地在
时间算出
lowbit(x) = x & (~ x + 1 ) = x & (-x)

结论:

维护的区间为

有了上述结论,我们进入第二个重点

如何使用

维护的信息去计算
  1. 原问题转换,
    ,问题转换成对于任何
    ,计算
  2. 如何计算
    ?我们怎么知道
    区间和是由哪些
    维护的?

这就是树状数组强大的地方,让我们首先找到

,它维护了
,既然这不断地更新
,直到
不就行了?也就是从
开始,把
维护的区间和加入到答案中,再更新
维护区间的左端点-1。

至此,我们理解了如何使用数状数组计算单点修改的区间和。

区间修改稍微复杂一点,后面会有介绍。

实现

  1. 初始化

直接把

全部赋0即可,再通过update(int pos, int val)函数更新
memset

更新操作

即把

,更新了
我们还要去维护它影响的区间,我们如何知道
影响了哪些区间?

我们知道

一定维护了
,所以想办法计算
不断增大的过程中经过了哪些区间。

从上图可知,如果更新了

,那么有4个区间被影响了需要修改,即
,可以按以下步骤去理解:
  1. ,更新
    ,它维护区间一定包含
    ,下一个区间的左端点是
    ,此区间对应的右端点(也就是下标)是多少呢?

    设右端点
    ,对应的区间长度是
    ,则这个表达式成立

    这就是知道区间右端点,去计算左端点的逆操作,一个减掉
    ,一个加上

    我们把
    加上它的
    就完成了:
    ,更新
  2. ,加上
    和第一步同样的计算方式
  3. ,加上
    ,更新

,加上
,更新
,结束。
void 

求和操作

我们已经发现,更新操作是从

,每一次

求和操作是

,每一次找到
的左端点,再减1,直到
,即

那么代码也就很简单了

int 

对于任意

,可以保证维护
的区间数量小于等于

并且求和

也可以被表示成最多
段区间的和,因此更新和求和操作都是
级别的,相比暴力算法,在时间复度上降低很多。空间复杂度上,也只需要
大小的数组维护,常数很小,比线段树代码更少,写起来更简单。

进阶

区间最值

考虑以下问题

给定数组

,长度
,支持以下三种操作
  1. 更新为
  2. 求区间
    中的最大值

我们把区间求和问题进阶一下,变成了区间最值问题(最小值同理),对应的,数状数组的定义发生了改变。

的含义不再是
区间的和,而是
中所有元素的最大值。

遗憾的是,不能简单地更改求和函数来达到目的。求和函数中,不同区间是彼此不向交的,而最值的性质则需要利用区间之间的“包含”关系。

745b5129452bcece7f5fc3a72ae714c9.png

初始化和求和一样,全部赋0即可,再调用update(pos, val)更新。

  1. 更新

更新了

后,
的值不一定会改变,因为最大值可能来自它的其他子区间。

最值要求的是所有

子区间的最值,那么如何求
的子区间呢?

上图是

的子区间构成情况。

这就是个找规律题:设给定

void 

可惜

没有办法像
那样分解。因此我们从
入手,一边缩小
的值
,一边更新答案。

已知

是区间
的最大值,如果
即可以将最大值更新为
,再缩小
的范围

否则表示

在区间内部,怎么办呢?只能委屈

例如,我们要求

的区间最值

08135aa83cee93638b1d8a4500f43679.png
  1. :
  2. :结束

可以观察到,红色区间都是放弃求值的,最终求的只有黄色部分

int 

最小值也同理,因此我们可以使用树状数组解决区间和、区间最值、区间极值等问题。

整个数列第k大

TODO

区间修改

之前说过,树状数组对于区间修改的问题基本无力,但需要一些技巧。

线段树倒是很适合这个问题,不过需要使用懒惰标记来实现,挺麻烦的。

给定数组

,长度
,支持两种操作
  1. 将区间
    元素加上

常规的树状数组肯定不行了,所以我们需要引入差分数组

查分数组

,满足

因此

于是我们用树状数组

维护差分数组的区间和
  1. 区间更新单点询问

对于区间更新操作,在

上加上
,我们看看差分数组的变化

更新后变成

只有两项发生了变化

于是,区间

的修改变成对差分数组
的两次单点修改操作。

那么

的值就很好算了,
数组又是树状数组维护的。
void 

为什么回答

是多少要用query函数?直接返回
不就行了?不要忘记,我们一直更新和维护的是
差分数组,所有的更新都没有更新到
去哦,所以自然要query函数去计算了。
  1. 区间修改区间查询

根据差分数组的定义我们可以得到如下表达式

那么就能推出

我们令一个前缀和系数数组

,提前算好,于是

这样,前项由树状数组维护的

求出,后项
通过更新维护后可以
算出

但是 注意,

更新后,
也应该更新,更新成什么呢?
  1. 也代表的是前缀和,当我们给
    加上
    时,意味着给
    要加上
    ,并更新影响的前缀和

综上,代码如下

void 

习题讲解

  1. 区间求和模版题
    题目地址 注意:各组测试样例间数据不能清0

单点更新,区间查询的模版题,可以直接上区间更新代码,要注意两点

  1. 单点更新并不是直接
    ,而是
    ,因此我们要实现单点查询
    ,再更新update(i, x-A[x])和update(i+1, A[x]-x)
  2. 数据证明,树状数组是真的快。
    1. 单点修改区间查询的树状数组:748ms 代码
    2. 区间修改区间查询的树状数组:794ms 代码
    3. zkw线段树:826ms 代码
  1. 题目链接 地址

在数轴上给定

个点,每个点在
时刻位置
,以速度
不断移动。对任意两个不同点,
位置不断随时间变化,定义
为它们的距离最小值。求

显然暴力

是过不了的,因此考虑节点
对答案的贡献

=
左边且速度小于等于
的点与
的初始距离和
右边且速度大于
的点与$i$的初始距离和。

当然了还有其他地方需要考虑:速度离散化处理,还需要维护某段区间上被加的个数等。

AC代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值