首页 > 其他分享 >树状数组

树状数组

时间:2024-02-05 14:46:50浏览次数:26  
标签:前缀 树状 lowbit 修改 数组 dp

【树状数组是什么】

树状数组(Binary Indexed Tree, BIT)

支持单个元素修改 和 前缀查询。

比较一下:

子段和 修改单个元素
数组 \(O(n)\) \(O(1)\)
前缀和 \(O(1)\) \(O(n)\)
树状数组 \(O(\log n)\) \(O(\log n)\)

【树状数组的实现】

比如 \(a\) 数组有 16 个元素。

现在我们定义树状数组 \(t\) 。

\(t[i]\) 表示 \(\sum_{j=i-lowbit(i)+1}^{i}\)。

其中 \(lowbit(x)=k\) 表示 \(2^k\mid x\) 但是 \(2^{k+1}\nmid x\) ,即二进制下最低的是 1 的位 所代表的数。

形象一点:

先把所有是 16 的倍数的下标取出,它们的值是以下标结尾的 16 个元素的和。

在把 8 的倍数取出,同样,但是 16 的倍数就不管了。

循环往复,直到取完 1 的倍数。


如上图,就是 16 个元素的例子。


经过这样的处理,查询前缀和的复杂度是 \(\log\) 级别 。

\(s[x]=s[x-lowbit(x)] + t[x]\),而 \(s[x-lowbit(x)]\) 又是一个同类型但更简单的子问题,递归求和即可。

相当于把 \(x\) 二进制拆位,因为 \(x-lowbit(x)\) 就直接消去了最后一个 1,下一次的 \(lowbit()\) 就至少乘 2,所以查询前缀和的复杂度是 \(\log n\)。

单个元素修改的复杂度也是 \(\log\) 级别

假设我们要修改 \(a[num]\) 。

首先所有下标小于 \(num\) 的 \(t_i\) 一定不用修改。

然后 \(t_{num}\) 一定要修改。

再之后,如果 \(t_x\) 要修改,那么 \(t_{x+lowbit(x)}\) 也要修改。

证明:

\(t_{x+lowbit(x)}\) 的管辖范围至少是 \(t_x\) 的 2 倍。

而 \(t_x\) 的管辖范围就是 \(lowbit(x)\)。

所以 \(t_{x+lowbit(x)}\) 可以管辖 \(a[x],a[x+1],...,a[x+lowbit(x)]\),还可以管辖 \(t_x\) 的所有。

而 \(t_x\) 可以管到 \(a[num]\),所以 \(t_{x+lowbit(x)}\) 也要修改。

接着还有:除了上述元素,其他的元素都不用修改。

证明:

假设 \(t_x\) 要修改,按照方法下一个修改的应该是 \(t_{x+lowbit(x)}\)。

我们只需要证明 \(t_{x+1},t_{x+2},...,t_{x+lowbit(x)-1}\) 都不修改即可。

而因为 \(x+lowbit(x)\) 恰使管辖范围增加,所以 \(x+1,x+2,...,x+lowbit(x)-1\) 都不能使管辖范围增加。

范围不增加,终点还远了,当然管不到 \(a_{num}\)。

综上,我们只需要按照每次增加 \(lowbit(x)\) 的方式修改即可。

最后小问题:\(lowbit(x)\) 怎么算?

\(lowbit(x)=x\) & \((-x)\),按照补码算一下就证出来了。

【优缺点】

优点:

代码短、常数小。

缺点:

单点修改,可加不可减(比如求一个子段的最值,不能大减小),无法求任意子段,只能求前缀和。

【拓展】

  1. 树状数组优化 dp

一部分 dp 在转移的时候都是 \(dp[i]=max\{dp[j]|j\in[1,i)\}+k\) 的形式。

以前我们要用一重循环枚举 j,但是现在我们可以用树状数组快速求 \(dp[1]\) ~ \(dp[i - 1]\) 的最值,这刚好是一个前缀。

例子:最长上升子序列

先离散化。

\(dp[i]\) 表示以 \(i\) 号元素结尾的最长上升子序列长度。

当进行第 \(i\) 次循环,求出了 \(dp[i]\) 后,\(tr[a[i]]\) 和 \(dp[i]\) 取 max,再赋值到 \(tr[a[i]]\)。

这样我们要求 \(dp[i]\) 时,我们只需要求 \(tr[1]\) ~ $ tr[a[i] - 1]$ 的 \(max\) 再加一即可。

  1. Generic Cow Protests G

基本想法:\(dp[i]\) 表示前 \(i\) 个分成的方案数。

\(dp[i]\) = \(\sum dp[j]\),\(j\in [1,i)\),且 \(s[i]-s[j]\geq 0\),\(s[i]\) 表示 \(a[1]+a[2]+...+a[i]\)。

注意:\(s[i]\geq s[j]\),考虑树状数组。

\(s[i]\) 为位置,\(dp[i]\) 为值。

标签:前缀,树状,lowbit,修改,数组,dp
From: https://www.cnblogs.com/FLY-lai/p/18007943

相关文章

  • 34 数组操作符的重载
    数组访问的一些思考string类最大限度地考虑了C字符串的兼容性。可以按照使用C字符串的方式适应string对象。#include<iostream>#include"add.h"usingnamespacestd;intmain(void){strings="safbd1334";intn=0;for(inti=0;i<s.length()......
  • 数据结构之——数组
    数组数据结构的基本类型之一,它可以构成其他数据结构,如栈、队列、哈希表、树、图、堆、矩阵、张量。数组在内存中的存储方式为一片连续的内存空间,其基本操作与其他数据结构一致,即所谓的增删改查。废话不多说,上代码加以理解。Java类型实现classarray{publicstaticvoid......
  • 乘积小于k的子数组
    问题描述:给定一个正整数数组nums。找出该数组内乘积小于k的连续的子数组的个数。示例1:输入:nums=[10,5,2,6],k=100输出:8解释:8个乘积小于100的子数组分别为:[10],[5],[2],[6],[10,5],[5,2],[2,6],[5,2,6]。需要注意的是[10,5,2]并不是乘积小于100的......
  • 长度最小的子数组
    问题描述:给定一个含有n个正整数的数组和一个正整数s,找出该数组中满足其和≥s的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回0。示例:输入:s=7,nums=[2,3,1,2,4,3]输出:2解释:子数组[4,3]是该条件下的长度最小的连续子数组。进阶:如果你......
  • js 数组和对象的深拷贝的方法
    数组深拷贝的方法方法一:for循环实现vararr=[1,2,3,4,5]vararr2=copyArr(arr)functioncopyArr(arr){letres=[]for(leti=0;i<arr.length;i++){res.push(arr[i])}returnres} 方法二:slice方法原理也比较好理解,他是将原数......
  • 树状数组
    树状数组总结前言树状数组是数据结构中的一股清流,代码简洁,思路清晰,又好理解qwq。前置芝士lowbit:https://www.cnblogs.com/zhouruoheng/p/18003331简介树状数组是一种基于lowbit的用于维护\(n\)个数前缀和信息的数据结构。支持:快速求前缀和,复杂度为\(O(\log{n})\)。......
  • 寻找两个有序数组的中位数(4)
    4MedianofTwoSortedArrays问题描述:给定两个大小为m和n的有序数组nums1和nums2。请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为O(log(m+n))。你可以假设nums1和nums2不会同时为空。示例1:nums1=[1,3]nums2=[2]则中位数是2.0示例2:n......
  • 两数之和-输出有序数组
    167TwoSumII-Inputarrayissorted问题描述:给定一个已按照升序排列的有序数组,找到两个数使得它们相加之和等于目标数。函数应该返回这两个下标值index1和index2,其中index1必须小于index2。说明:返回的下标值(index1和index2)不是从零开始的。你可以假设每个输入......
  • 合并两个有序数组
    88.合并两个有序数组问题描述:给定两个有序整数数组nums1和nums2,将nums2合并到nums1中,使得num1成为一个有序数组。说明:初始化nums1和nums2的元素数量分别为m和n。你可以假设nums1有足够的空间(空间大小大于或等于m+n)来保存nums2中的元素。示例:输......
  • 根据筛法规则对整数分类,建立树状结构
    筛法目前一般用来找整数序列中的素数,不是素数的元素被丢掉了。如果仅把筛法当成一种分类规则,把筛掉的元素和留下的元素算作不同的分类,并用每一类中的最小元素递归地执行筛法,那么能把所有正整数保留下来,并建立一个树状结构。例如,初始集合是正整数集,根据模最小元素p是否为0,可把所有......