倍增搜索

二分搜索适合既有下界也有上界,对于只知道一边的界情况,可以用倍增搜索法

int search(vector<int> A,  int x) {
    for (int i = 0; A[i] <= x;) {
        if (A[i] == x) return i;
        if (i + 1 == A.size() || A[i + 1] > x) return -1;
        for (int k = 1; i + k < A.size() && A[i + k] <= x;  k += k)
            i += k;
    }
    return -1;
}

类似问题有用除法的定义(只用加法)实现除法。

循环不变式:如果数组中存在x, 它必然在大于等于位置i 的地方

算法框架:

1)如果起点处i的值A[i] > x,否则说明这个范围内不存在x, 返回-1

2)如果起点A[i]==x,找到,返回位置i

      否则就是A[i] < x:

          这时候如果 A[i + 1] > x, 也说明不存在,返回-1

  否则进行倍增查找,当i跳某个步长k后的值A[i+k] 大于x ,本轮倍增结束,一直保持着不变式 A[i] <= x ,主循环下一轮相当于是解决一个相同的问题,只不过i已经推进了很多,离目标近了一些。用递归看更清晰:

int search(vector<int> &A,  int i, int x) {
    if (A[i] > x) return -1;
    if (A[i] == x) return i;
    if (i + 1 == A.size() || A[i + 1] > x) return -1;
    for (int k = 1; i + k < A.size() && A[i + k] <= x; i += k, k += k);
    return search(A, i, x);
}


另一种更通用的模板:


int search(vector<int> A,  int x) {
    int i = -1; //i is the farest position currently searched
    for (;;) {
        int k = 1;
        for (; i + k < A.size() && A[i + k] <= x;  k += k) {
            i += k;
        }
        if (i >= 0 && A[i] == x) return i;
        if (k == 1) return -1;
    }
}


bool isPowerOfThree(int n) {
        int x = 1; //closed position that not exceed
        for (;;) {
            int step = 3;
            for (; n / step >= x; step *= step) {
                x *= step;
            }//exit: could not add the current step
            if (x == n) return true;
            if (step == 3) return false; //could not go one step further, or will exceed
        }
    }





### 倍增法的概念与应用 #### 1. **倍增法的定义** 倍增法是一种通过逐步翻倍的方式解决问题的算法思想,其核心在于利用已经计算过的中间结果来减少重复计算[^1]。这种方法在很多场景中能够显著降低时间复杂度,尤其是在需要多次查询或递归操作的情况下。 #### 2. **倍增法的核心原理** 倍增法的核心是将问题分解为多个阶段,在每个阶段中,通过前一阶段的结果快速推导出当前阶段的结果。例如,在解决RMQ(Range Minimum/Maximum Query)问题时,可以通过预先计算区间长度为$2^k$的最小值或最大值,从而在查询任意区间时快速得出答案。 #### 3. **倍增法的应用场景** ##### (1) **RMQ问题** RMQ问题是指在一个数组中,快速查询任意区间内的最小值或最大值。倍增法通过构建一个二维数组`f[i][j]`,其中`f[i][j]`表示从第$i$个元素开始,长度为$2^j$的区间的最小值或最大值。通过这种方式,可以在预处理后实现$O(1)$的时间复杂度查询任意区间。 ```python def preprocess_rmq(arr, n): # 初始化 f[i][0] f = [[0] * 20 for _ in range(n)] for i in range(n): f[i][0] = arr[i] # 动态规划填充 f[i][j] for j in range(1, 20): for i in range(n - (1 << j) + 1): f[i][j] = min(f[i][j-1], f[i + (1 << (j-1))][j-1]) return f def query_rmq(f, l, r): k = int(math.log2(r - l + 1)) return min(f[l][k], f[r - (1 << k) + 1][k]) ``` ##### (2) **LCA问题** 最近公共祖先(LCA,Lowest Common Ancestor)问题是求解树上两个节点的最近公共祖先。倍增法通过预处理每个节点向上跳$2^k$步的父亲节点,从而在查询时快速找到最近公共祖先[^1]。 ```python def preprocess_lca(parent, depth, log_n, n): # 初始化 jump[i][0] jump = [[-1] * log_n for _ in range(n)] for i in range(n): jump[i][0] = parent[i] # 动态规划填充 jump[i][j] for j in range(1, log_n): for i in range(n): if jump[i][j-1] != -1: jump[i][j] = jump[jump[i][j-1]][j-1] def find_lca(jump, depth, u, v, log_n): if depth[u] < depth[v]: u, v = v, u # 将 u 和 v 调整到同一深度 for i in range(log_n-1, -1, -1): if depth[u] - (1 << i) >= depth[v]: u = jump[u][i] if u == v: return u # 向上跳,直到两者相遇 for i in range(log_n-1, -1, -1): if jump[u][i] != -1 and jump[u][i] != jump[v][i]: u = jump[u][i] v = jump[v][i] return jump[u][0] ``` ##### (3) **依赖解析优化** 在Maven依赖解析中,倍增法可以用于加速冲突解决过程。通过广度优先搜索结合倍增的思想,可以快速确定离根节点最近的有效依赖版本[^3]。 ##### (4) **后缀数组构造** 倍增法在后缀数组的构造中也有广泛应用。通过逐步扩展比较的长度(从1到$2^k$),可以在$O(n \log n)$的时间复杂度内完成后缀数组的构造[^4]。 ```cpp void da(int *r, int *sa, int n, int m) { int i, j, p, *x = wa, *y = wb, *t; for(i = 0; i < m; i++) ws[i] = 0; for(i = 0; i < n; i++) ws[x[i] = r[i]]++; for(i = 1; i < m; i++) ws[i] += ws[i-1]; for(i = n-1; i >= 0; i--) sa[--ws[x[i]]] = i; for(j = 1, p = 1; p < n; j *= 2, m = p) { for(p = 0, i = n-j; i < n; i++) y[p++] = i; for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i]-j; for(i = 0; i < n; i++) wv[i] = x[y[i]]; for(i = 0; i < m; i++) ws[i] = 0; for(i = 0; i < n; i++) ws[wv[i]]++; for(i = 1; i < m; i++) ws[i] += ws[i-1]; for(i = n-1; i >= 0; i--) sa[--ws[wv[i]]] = y[i]; t = x, x = y, y = t; for(p = 1, x[sa[0]] = 0, i = 1; i < n; i++) x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p-1 : p++; } } ``` --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值