OI KIWI 01-倍增
思想
查找小于limit的最大数字
1 | int maxValueInVecSmallerThenLimit(vector<int>& vec, int limit) { |
和二分一样,需要在有序数组上查找
对于查找区间
[l, l + p)
- 如果
vec[l+p] >= limit
, 则最大值就在[l, l + p)
区间上,下一步查询[l, l + p / 2)
- 如果
vec[l+p] < limit
, 则最大值不在[l, l + p)
区间上,下一步查询[l + p, l + 3*p)
- 如果
l+p >= n
, 则缩小查找范围
- 如果
我们把上面的逻辑迭代两次
- 如果
vec[l+p] >= limit
, 则最大值就在[l, l + p)
区间上,下一步查询[l, l + p / 2)
- 如果
vec[l+p/2] >= limit
, 则最大值就在[l, l + p/2)
区间上,下一步查询[l, l + p / 4)
- 如果
vec[l+p/2] < limit
, 则最大值不在[l, l + p/2)
区间上,下一步查询[l + p/2, l + p/2 + p)
- 如果
- 如果
vec[l+p] < limit
, 则最大值不在[l, l + p)
区间上,下一步查询[l + p, l + 3*p)
- 如果
vec[l+3*p] >= limit
, 则最大值就在[l + p, l + 3*p)
区间上,下一步查询[l + p, l + 2*p)
- 如果
vec[l+3*p] < limit
, 则最大值不在[l + p, l + 3*p)
区间上,下一步查询[l + 3 * p, l + 7 * p)
- 如果
- 如果
RMQ区间最值
Range Maximum/Minimum Query
单调栈
用单调栈找到两个数组left
和right
left[i]
代表arr[i]
在[left[i], i]
的区间上是最小值right[i]
代表arr[i]
在[i, right[i]]
的区间上是最小值- 对于一个查询
[l, r]
- 如果
left[r] <= l
,arr[r]
是区间最小值 - 如果
right[l] >= r
,arr[l]
是区间最小值 - 否则
l = right[l] + 1
,r = left[r] - 1
,缩小查找范围
- 如果
1 | vector<int> RangeMinimumQuery(vector<int>& arr, vector<vector<int>>& queries) { |
ST表
参考这里:https://oi-wiki.org/ds/sparse-table/
适用范围:可重复贡献问题
- op(x, x) = x, 一个操作重复计算等于其本身
- 比如max, min, gcd等操作
- 这样可以允许我们划分子问题时,即使子问题之间存在重叠,也可以获得正确的结果
$ ST[i][j] = min(arr[i…(i + 2^j - 1)]) $ (闭区间)
1 | /* |
可见构造的时间为O(nlogn)
1 | /* |
一个查询有可拆成同一行的两个子数组
因为 $ ST[i][j] = min(arr[i…(i + 2^j - 1)]) $
$ start = i $ , $end = i + 2^j - 1$
解得
$j = log(end - start + 1)$
要查 $ 0 … 6 $,查询 $ (0, log(6-0+1)) $ = $ (0, 2) $ = $ 0 … 3 $
下面查 $ 3 … 6 $,查询 $ (3, 2) $ = $ (6 - 2^2 + 1, 2) $
公式是这么来的:
$a…b$ = $(a, a + 2^j - 1)$ = $(b - 2^j + 1, b)$
要查$0 … 5$,查询$(0, log(5-0+1))$ = $(0, 2)$ = $0 … 3$
下面查$ 2 … 5 $, 查询$(2, 2)$ = $(5 - 2^2 + 1, 2)$
也就是说,要查询$ a…b $
相当于$ min(a…(a+2^j-1), (b - 2^j + 1)…b) $
$ min(ST[a][j], ST[b-2^j+1][j]), j = log2(b-a+1)$
如何构造ST表:
$ (i, i + 2^j - 1) = (i, i + 2^{j-1} - 1), (i + 2^{j-1}, i + 2^j - 1) $
$ ST[i][j] = min(ST[i][j-1], ST[i+j][j-1]) $
1 | vector<int> RMQ(vector<int>& array, vector<pair<int, int>>& query) { |
快速幂
对指数进行二进制分解
$ n $ = 22 = 10110 = $2^4 + 2^2 + 2^1$
$ a^{n} = a^{2^4} \times a^{2^2} \times a^{2^1}$
1 | int fastPow(int base, int pow, int mod) { |
LCA最近公共祖先
遍历二叉树
1 | class Solution { |
复杂度为$ O(n) $
对于$m$次查询,复杂度为$O(m \times n)$
ST表
当查找两个节点node0和node1的最近公共祖先时
- 如果node0和node1在同一层,深度为
d
- 如果两个节点在深度
d-k
层处的祖先是同一个- 那么说明最近公共祖先在
[d-k, d+1]
k
偏大了,需要减小
- 那么说明最近公共祖先在
- 如果两个节点在深度
d-k
层处的祖先不是同一个- 那么说明最近公共祖先在
[0, d-k]
- 将两个节点移动到各自
d-k
层处的祖先处
- 那么说明最近公共祖先在
- 如果两个节点在深度
如此迭代,就可以将区间不断缩小,最后定位到最近公共祖先
如果k
取当前深度的一半,就可以达到log(depth)
的复杂度
ST[i][j]
表示节点i
向上2^j
层的祖先节点
$ 2^j < depth, j=0…floor(log2(depth))$
转移方程为:
1 | ST[i][j] = ST[ST[i][j-1]][j-1] |
这里就用到了倍增的思想
- 如果node0和node1在不同一层,可以先将深度较深的节点向上移动
假设高度差为5=101
则将高度差的二进制分解,即可以1
2node = ST[node][0] // j = 0, 2^0
node = ST[node][2] // j = 2, 2^2log(dep1 - dep2)
的复杂度将两个节点放到同一层
1 | class LCA { |
- 每次查询的复杂度为
- $O(log(depth))$
- 预处理的时间复杂度为
- $O(log(n \times log(depth)))$
- $ m $ 次查询的复杂度为
- $ O((m + n) \times log(depth)) $