- 主要内容
- 它俩下标都从1开始
- 树状数组:
- 动态快速求前缀和,单点修改(某个位置上加上一个数),区间查询(前缀和)
- 时间复杂度:O(logN)
lowbit()
,add(int x,int v)
:原数组的下标x的值加上v,query(int x)
:查询原数组下标1到x的前缀和
- 线段树:
- 能处理的范围更大,包含树状数组,比树状数组慢;单点修改,区间查询
- 一个长度为n的区间,线段树节点总个数小于等于4n;定义每个节点(左边界、右边界、某个属性)
pushup
:用子结点信息 更新 当前节点的信息;build
:在一段区间上初始化线段树
;modify
:修改;query
:查询
树状数组
- 可以动态地、快速 求前缀和 ; logN
- 给某个位置的数 加上 一个数(单点修改)
- 求某一个前缀和 (区间查询)
- 它只能解上述单点修改和区间查询问题,其他问题要转换成这种类型的才可以解
- 数组下标从1开始
- 树状数组相当于一个辅助数组,不要把树状数组和原数组混淆,我们要求的是原数组的前缀和 要求的是在原数组加上某个数,然后借用一个树状数组进行操作,而不是在原数组上操作
lowbit(x) = x & -x = 2^k
:其中k:x末尾0的个数
AcWing1264 动态求连续区间和(树状数组求解)
代码
1 |
|
AcWing1265 数星星
题意
概括:给定 N 个点,定义每个点的等级是在该点左下方(含正左、正下)的点的数目,试统计每个等级有多少个点。
思路
- 动态输入每个点的坐标的时候,对于当前刚输入的点的坐标,它的左下方的坐标存在的星星数量刚好是 当前输入点x方向 左方的所有坐标存在的星星数量
- 因此,只需要求出当前x坐标的左边,即:从坐标1到x上的星星 个数总和即可;即可转换成求前缀和;
- 由于每输入一个星星坐标,对于前缀和数组,该星星后面的前缀和数组的值都会+1,所以可以看成一个动态区间求前缀和的 问题,因此完全可以转换成树状数组来求解
代码
1 |
|
AcWing1215 小朋友排队
题意
给定一个数组,要求从小到大排序,每次只能交换相邻位置。定义一个sum
当原数组的第i个数总共被交换了k次,sum+=(1+k)*k/2
求最小的sum
思路
从小到大,交换相邻:冒泡排序,但是数据范围1≤n≤100000,冒泡里面再加一层for的话会爆
n≤100000推出算法复杂度最多是O(nlogn) => 截止写这个题解之前本fw学过的可能有各种sort,线段树、树状数组、二分…..
比如给定一个数组:
3 4 2 1 4
;对于2前面的数来说,只要比2大,必然会被交换一次;对于2后面的数来说,只要比2小,也必然会被交换依次;可以推出,对于第i个数,被交换的次数就是
k1+k2
;故关键在于,对于第i个数,求出它前面比它大的数的个数k1 ,求出它后面比它小的数的个数k2
针对一个k1或者k2会被计算两次,故,求总的交换次数是所有k之和除以2
假设存在一个数组b[i](树状数组的原数组):存放身高==i的小朋友个数,(关键在于是:个数!!)
对于k1:对于一组已知的小朋友的身高,我们可以一个个处理,比如,当处理到第x个小朋友的身高时(此时并不理会x后面的小朋友的身高),在此前,将前面的x-1小朋友的身高就可以存在b[i](身高=i 的个数) 中,这个b[i]其实用一个树状数组来维护(因此不需要表示出b这个数组)
query(int a)
则表示求出 身高在 1到a 之间的小朋友的个数- 若要求前面x-1个小朋友身高大于第x个小朋友身高(假设为hh)的个数,即
query(N)-query(hh-1)
- 即是当前,身高在1到N之间小朋友的个数 减去 身高≤hh的小朋友个数
同理可得k2
代码
1 |
|
线段树
- 线段树能处理的范围包含树状数组
- 代码短、常数小
- 求区间最大值,面积,长度
- 对于一个长度为n的区间,线段树节点总个数小于等于4n
- 存储到数组里面,对于下标x
- 父节点 x/2 (下取整),即是
x>>1
- 左子结点:2x ,即是
x<<1
- 右子结点:2x+1,即是
x<<1|1
- 父节点 x/2 (下取整),即是
- 比如对于上述维护总和的操作有
- 单点修改
- 区间查询 O(logN)
1 | //pushup:用子结点信息 更新 当前节点的信息;可能有时候比较简单,直接写到别的函数内部去 |
AcWing1264 动态求连续区间和(线段树求解)
题意
给定一个数组,多个操作:修改某个元素,求子数组的连续和
思路
设计区间的单点修改和查询,考虑线段树
代码
1 |
|
AcWing1270 数列区间求最大值
题意
一个数组,M个询问,每次查询第X到Y个数字的这个区间中的最大值
思路
- 因为1≤N≤1e5,1≤M≤1e6,如果直接暴力,有M个询问,对于每个询问,遍历一遍x到y之间的数据,双重循环显然会爆,
- 但它涉及到对一个区间的查询,且根据数据范围可知时间复杂度最多为nlogn,所以考虑使用线段树
代码
1 |
|