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值,在询问时直接使用。

相关推荐
hetao173383716 小时前
2026-10-22~23 hetao1733837 的刷题记录
c++·算法
java_nnnn16 小时前
LeetCode刷题滑动窗口(串联所有单词的子串+最小覆盖的子串)Java
算法·leetcode·职场和发展
王老师青少年编程17 小时前
2025年6月GESP真题及题解(C++三级): 选择题和判断题(题解)
c++·真题·gesp·答案·csp·三级·信奥赛
Navigator_Z17 小时前
LeetCode //C - 1301. Number of Paths with Max Score
c语言·算法·leetcode
卡卡~~17 小时前
“101计划”核心课程实践教材《数据实验结构指导-C++语言版》题目集
开发语言·c++·学习·算法
企业数字化笔记18 小时前
AI视频增强后画质怎么验收?插值基线、伪细节与帧间闪烁的检查方法
人工智能·算法·音视频
学逆向的18 小时前
C 内存属性
c语言·开发语言·算法·网络安全
lengxuenong19 小时前
树状数组 · 信息学学习手册1-求和3个模板
c++·学习·算法·青少年编程
ComputerInBook19 小时前
泛函分析概观
人工智能·算法·机器学习·泛函分析