倍增
倍增其实就是和二分反过来的方法,它几乎就是与二分相对立的。倍增是一直翻倍,二分是一直折半,但两者的时间复杂度都是 O ( log 2 n ) O(\log_2n) O(log2n) 的,因此不管是哪种方法都要好好学习。
倍增,顾名思义,就是通过成倍的增长以达到降低时间复杂度的方法,但在现实中,我们一般都不会直接的乘 2 2 2,而是利用二进制展开,举个很简单的例子:
洛谷P4155 [SCOI2015] 国旗计划
题目描述:A 国正在开展一项伟大的计划 —— 国旗计划。这项计划的内容是边防战士手举国旗环绕边境线奔袭一圈。这项计划需要多名边防战士以接力的形式共同完成,为此,国土安全局已经挑选了 N N N 名优秀的边防战士作为这项计划的候选人。A 国幅员辽阔,边境线上设有 M M M 个边防站,顺时针编号 1 1 1 至 M M M。每名边防战士常驻两个边防站,并且善于在这两个边防站之间长途奔袭,我们称这两个边防站之间的路程是这个边防战士的奔袭区间。 N N N 名边防战士都是精心挑选的,身体素质极佳,所以每名边防战士的奔袭区间都不会被其他边防战士的奔袭区间所包含。现在,国土安全局局长希望知道,至少需要多少名边防战士,才能使得他们的奔袭区间覆盖全部的边境线,从而顺利地完成国旗计划。不仅如此,安全局局长还希望知道更详细的信息:对于每一名边防战士,在他必须参加国旗计划的前提下,至少需要多少名边防战士才能覆盖全部边境线,从而顺利地完成国旗计划。
输入格式:第一行,包含两个正整数 N , M N,M N,M,分别表示边防战士数量和边防站数量。随后 N N N 行,每行包含两个正整数。其中第 i i i 行包含的两个正整数 C i C_i Ci、 D i D_i Di 分别表示 i i i 号边防战士常驻的两个边防站编号, C i C_i Ci 号边防站沿顺时针方向至 D i D_i Di 。
输出格式:输出数据仅 1 1 1 行,需要包含 N N N 个正整数。其中,第 j j j 个正整数表示 j j j 号边防战士必须参加的前提下至少需要多少名边防战士才能顺利地完成国旗计划。号边防站力他的奔袭区间。数据保证整个边境线都是可被覆盖的。
数据范围: N ≤ 2 × 1 0 5 , m < 1 0 9 , 1 ≤ c i , d i ≤ m N\le 2\times10^5,m\lt 10^9,1\le c_i,d_i\le m N≤2×105,m<109,1≤ci,di≤m。
题目要求很清晰:让我们求最少多少个战士能覆盖整个圆圈。
看到这种题首先很容易想到贪心和化圆为线,只需要把每个区间的左端点排一下序,然后就可以开始做了。
时间复杂度:用贪心查询一次是 O ( n ) O(n) O(n),把每个区间作为起点共 n n n 次查询,总时间复杂度 O ( n 2 ) O(n^2) O(n2),明显超时。
这是就需要用到倍增法来优化了,使用倍增之前要跑 4 × 1 0 10 4\times10^{10} 4×1010,倍增之后就只有 3 × 1 0 6 3\times10^6 3×106 左右了,这是怎么做到的呢?
首先我们定义一个数组 to[s][i]
,表示从第
s
s
s 个区间开始往后跳
2
i
2^i
2i 个最优区间后能到达的区间,那么我们就会有以下这个巧妙地递推式:
t o [ s ] [ i ] = t o [ t o [ s ] [ i − 1 ] ] [ i − 1 ] to[s][i]=to[to[s][i-1]][i-1] to[s][i]=to[to[s][i−1]][i−1]
这也很好理解,毕竟我走了 2 i 2^i 2i 个区间就相当于我先走 2 i − 1 2^{i-1} 2i−1 个区间后再走 2 i − 1 2^{i-1} 2i−1 个区间。
这样,这道题就被我们完美的转化成了一道类似于递推的题。
特别的,初始化 to[s][0]
也就是往后走
1
1
1 个区间后的最优解,这可以用贪心求解。而最后的查询也只需要
O
(
log
2
n
)
O(\log_2n)
O(log2n),一共
n
n
n 次,所以总的时间复杂度算上预处理的时间复杂度就是
O
(
n
log
2
n
)
+
O
(
n
log
2
n
)
O(n\log_2n)+O(n\log_2n)
O(nlog2n)+O(nlog2n)。
(代码自己看题解去)。
ST表
ST(Space Table)表是一种基于倍增法的算法,主要求解的是静态空间下的区间最值查询(Range Maximum/Minimum Query,简称 RMQ),它可以将 O ( n m ) O(nm) O(nm) 的时间复杂度(共 n n n 个数, m m m 次查询)降到 O ( n log 2 n ) O(n\log_2n) O(nlog2n) 级别,甚至查找最值都是 O ( m ) O(m) O(m) 的,具体步骤分为两步:
- 把整个数列分成很多个小区间,并提前计算出每个小区间的最值。
- 查询最值时,找到被他覆盖了的两个小区间并求出其中的最值。
我们分别来看。
倍增法分解成小区间。
首先我们看这样一个数组:
从倍增法的角度来讲,首先我们会把它拆成一个一个的,即 [4] [7] [9] [6] [3] [6] [4] [8] [7] [5]
。
然后再拆成两个两个的,即 [4 7] [7 9] [9 6] [6 3] [3 6] [6 4] [4 8] [8 7] [7 5]
。
接着翻倍:按四个四个拆,然后一直重复下去,直到超过了总长度。
我们设一个 dp[s][k]
表示某个区间的左端点为
s
s
s,经过了
2
k
2^k
2k 个单位长度后的区间最值,那 dp[s][0]
其实就是原本的数组,而对于 dp[s][k]
就会有下面这个完美的状态转移方程:
d p [ s ] [ k ] = min ( d p [ s ] [ k − 1 ] , d p [ s + ( 1 < < ( k − 1 ) ) ] [ k − 1 ] ) dp[s][k]=\min{(dp[s][k-1],dp[s+(1<<(k-1))][k-1])} dp[s][k]=min(dp[s][k−1],dp[s+(1<<(k−1))][k−1])
直接想不太好想,我们举个例子,还是拿上面那个样例说吧。
如果我要求 [4 7 9 6]
中的最大值,那我首先会拆成 [4 7]
和 [9 6]
两个区间,再取最大值的最大值,那其中的 dp[s][k-1]
就表示的是 [4 7]
,dp[s+(1<<(k-1))][k-1]
则是 [9 6]
,因为我以从
s
s
s 号点(即 4
)开始往后跳
2
k
−
1
2^{k-1}
2k−1 所在的位置(也就是 9
)为起点再往后跳
2
k
−
1
2^{k-1}
2k−1 中的最值就是 [9 6]
这个区间的最值。有点绕,想不明白的同学可以把脑袋抠破了再想。
查询最值
假设我们要查询 L , R L,R L,R 之间的最值,那我们按照上面 d p dp dp 的定义来推。
我们查询本质上是找出两个区间分别以 L L L 开头, R R R 结尾并且这两个区间要完美覆盖整个大区间(有重复也没事,具体原因下面会讲),我们来画个图辅助理解一下:
我们假设我们很倒霉,没法找到一个点就能满足上面条件,先假设我们找到两个点 x , y x,y x,y ,那下面这种情况是一定不可能成立的:
因为这张图中很明显并没有覆盖整个区间,而下面这种情况就是完全正确的:
为什么可以重复呢?答案很简单:因为重复的部分一模一样。
通俗点讲:就是因为重复的部分数字完全一样,所以他们之间比不出大小,谁都一样,所以重复算一遍相当于没算。
那我们怎么找 x x x 和 y y y 呢?
很简单的一个思路:我们只需要找到距离最短的时候这两个点所在的位置就行了。距离最短又是什么时候?我们回到
d
p
dp
dp 的定义上来,dp[s][k]
中的这个
k
k
k 表示走了
2
k
2^k
2k 个单位距离,那我现在总的距离为
R
−
L
+
1
R-L+1
R−L+1,所以
2
k
=
R
−
L
+
1
2^k=R-L+1
2k=R−L+1,由此推出
k
=
log
2
(
R
−
L
+
1
)
k=\log_2{(R-L+1)}
k=log2(R−L+1),我们向下取个整,就得到了最短距离。所以我们就推出了查询的公式:
min ( d p [ L ] [ k ] , d p [ r − ( 1 < < k ) + 1 ] [ k ] ) \min{(dp[L][k],dp[r-(1<<k)+1][k])} min(dp[L][k],dp[r−(1<<k)+1][k])
注意:上述公式中的
k
k
k 是改变了值之后的,不是之前的。 (为了防止一些和我一样健忘的人看不懂)
例题:洛谷P3865
LCA
求最近公共祖先(LCA)有很多种方法,今天我在这里介绍一种:倍增法求 LCA。
首先我们先建一棵树:
如果我们要求 4 4 4 号点和 3 3 3 号点的 LCA,那该怎么求呢?
我们人眼肯定一看就看出来答案是 1 1 1,但机器看不出来啊。一步一步往上跳?不可能,时间复杂度太高了。于是这是就用到倍增法来降低时间复杂度了。一般是三步:
- 深搜一遍,记录下每个点的深度。
- 记录下每个点的第 2 k 2^k 2k 个祖先。
- 查找两个点的 LCA。
深搜求深度
这一步很简单,没什么好说的,方法都写在标题上了,直接跳过。
记录
这一步就很有意思了。我们先开始初始化记录每个点的父亲 f a [ s ] [ 0 ] fa[s][0] fa[s][0],然后我们可以得到这样一个递推式:
f a [ x ] [ k ] = f a [ f a [ x ] [ k − 1 ] ] [ k − 1 ] fa[x][k]=fa[fa[x][k-1]][k-1] fa[x][k]=fa[fa[x][k−1]][k−1]
具体原因呢和先开始倍增法的公式很像,忘了的可以再回去看看。
查找 LCA
这是至关重要的一步:怎么 O ( log 2 n ) O(\log_2n) O(log2n) 求出 LCA呢?很简单,我们先把 k k k 从大到小遍历一遍,然后跳 2 k 2^k 2k 个祖先,也就是到 f a [ i ] [ k ] fa[i][k] fa[i][k],如果这个点和另一个点的值相等,表明我已经调到一个公共祖先,但却无法保证是不是最近的,所以应该算不执行此操作。如果不相等,就表明没跳到,应该跳到 f a [ i ] [ k ] fa[i][k] fa[i][k] 并继续循环。
最后经过很多次循环后,我们成功找到了他们的 LCA,的子节点,为啥呢?
我们上面说了,如果相等就不执行操作,也就是不跳,所以我们肯定不会到他们的 LCA,而是会很接近,而最接近的地方就是它的子节点了,所以最后再往上跳一个就行了。
代码很简单, 留着当课后习题了。
例题:洛谷P3379