0x06 倍增

0x06 倍增

倍增,字面意思就是"成倍增长"。我们在递推时,如果状态空间很大,通常的线性递推无法满足时间与空间复杂度的要求,那么我们可以使用成倍增长的方式,只递推状态空间在2的整数次幂上的值作为代表。当需要其他位置上的值时,我们通过"任意整数可以表示成若干个2的次幂项的和"这一性质,使用之前求出的代表值拼成所需的值。所以使用倍增算法也要求我们递推的问题的状态空间关于2的次幂具有可划分性。

"倍增"与"二进制划分"两个思想相互结合,降低了很多问题的时间与空间复杂度。我们之前学习的快速幂其实就是"倍增"与"二进制划分"思想的一种体现。

试想这样一个问题:

给定一个长度为N的数组A,然后进行若干次询问,每次给定一个整数T,求出最大的k,满足 ∑ i = 1 k A i ≤ T \sum_{i=1}^{k}Ai\leq T ∑i=1kAi≤T。你的算法必须是在线的(必须及时回答每一个询问),假设 0 ≤ T ≤ ∑ i = 1 N A i 0\leq T \leq \sum_{i=1}^{N}Ai 0≤T≤∑i=1NAi

最朴素的做法是从前往后枚举k,每次询问花费的时间与答案的大小有关,最坏情况下为O(n)

我们可以花费O(n)的时间预处理数组A,得到前缀和序列S,就可以二分k的位置,每次询问花费的时间为O(logn)。这个算法在平均情况下表现得非常好,但它的缺点是当每次给定的T都很小,造成答案的k值也很小,那么该算法可能还不如从前往后枚举更优。

我们可以设计一种倍增算法:

1.令p=1,k=0,sum=0;

2.比较"A数组k之后的p个数之和与"与T的关系,也就是说当 s u m + S k + p − S k ≤ T sum+Sk+p-Sk\leq T sum+Sk+p−Sk≤T ,则令 s u m + = S k + p − S k , k + = p , p ∗ = 2 sum+=Sk+p-Sk,k+=p,p*=2 sum+=Sk+p−Sk,k+=p,p∗=2,即累加上这p个数之和,然后把p的跨度增加一倍。如果 s u m + S k + p − S k > T sum+Sk+p-Sk>T sum+Sk+p−Sk>T,则令 p / = 2 p/=2 p/=2。

3.重复上一步直到p=0,此时k就是答案。

这个算法始终在答案范围内进行实施"倍增"和"二进制划分"思想,通过若干长度为2的次幂的区间拼成最后的k,时间复杂度为答案的对数,能够对应T的各种情况大小。

很多二分答案的想法都可以转化成倍增。

1. ST算法

在 R M Q RMQ RMQ问题中(区间最值问题),著名的ST算法就是倍增的产物。给定一个长度为N的序列A,ST算法能在O(NlogN)时间的预处理后,以O(1)的时间时间复杂度在线回答"数列A中下标在 l ∼ r l\sim r l∼r之间的最大值是多少"这样的区间最值问题。

一个序列的子区间有 N ∗ ( N + 1 ) 2 \frac{N*(N+1)}{2} 2N∗(N+1)个,根据倍增思想,我们首先在这个规模为 O ( N 2 ) O(N^2) O(N2)的状态空间中选择一些2的整数次幂的位置作为代表值。

设 F i j Fij Fij表示数列A中下标在子区间 i , i + 2 j − 1 i,i+2\^j-1 i,i+2j−1里的最大值,也就是从 i i i开始的 2 j 2^j 2j个数中的最大值。递推边界显然是 F i 0 = A i Fi0=Ai Fi0=Ai

在递推时,我们把区间的长度成倍增长,有公式 F i j = m a x ( F i j − 1 , F i + 2 j − 1 j − 1 ) Fij=max(Fij-1,Fi+2\^{j-1}j-1) Fij=max(Fij−1,Fi+2j−1j−1),即长度为 2 j 2^j 2j的子区间的最大值是左右两个长度为 2 j − 1 2^{j-1} 2j−1的子区间的最大值中较大的一个。

c++ 复制代码
void ST_prework()
{
    for (int i = 1; i <= N; ++i)
        f[i][0] = a[i];
    int t = log(N) / log(2) + 1;
    for (int j = 1; j < t; ++j)
        for (int i = 1; i <= N - (1 << j) + 1; ++i)
            f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}

当询问任意区间 l , r l,r l,r的最值时,我们先计算出一个k,满足 2 k ≤ r − l + 1 < 2 k + 1 2^k\leq r-l+1< 2^{k+1} 2k≤r−l+1<2k+1 ,也就是使2的k次幂小于等于区间长度前提下的最大k值。

c++ 复制代码
int ST_query(int l, int r)
{
    int k = log(r - l + 1) / log(2);
    return max(f[l][k], f[r - (1 << k) + 1][k]);
}

简便起见,我们在代码中使用了cmath库中的log函数。该函数效率较高,一般来说对程序性能影响不大。更严格地讲,为了保证复杂度在 O ( 1 ) O(1) O(1),应该 O ( N ) O(N) O(N)预处理出 1 ∼ N 1\sim N 1∼N这 N N N种区间长度各自对应的 k k k值,在询问时直接使用。

相关推荐
wljy19 小时前
二、进制状态转换
linux·运维·服务器·c语言·c++
云泽8089 小时前
笔试算法 -位运算篇(二):从唯一字符到消失数字
c++·算法·位运算
ʚ希希ɞ ྀ9 小时前
不同路径|| -- dp
算法
繁华落尽,倾城殇?9 小时前
[C++11] : atomic,nullptr,default/delete,enum class
开发语言·c++·c++11·nullptr·atomic·enum class·default/delete
代码村新手10 小时前
C++-二叉搜索树
开发语言·c++
IT 行者10 小时前
SimHash 与 MinHash:相似性计算的双子星算法
算法·hash·比对
智者知已应修善业10 小时前
【51单片机8位数码管动态显示日期小数点风格】2023-11-13
c++·经验分享·笔记·算法·51单片机
智者知已应修善业10 小时前
【51单片机有三个LED 分别第一个灯闪三下 再到第二个灯又闪三下 再到第三个灯又闪三下 就这样循环程序】2023-11-16
c++·经验分享·笔记·算法·51单片机
小娄~~12 小时前
C语言卷子错题集
c语言·开发语言·数据结构