快速幂

257 阅读1分钟

暴力破解

我们很容易想到 aba^b 可以采用 b 个 a 相乘来求结果。

但此种方法需要大量计算,时间复杂度为 O(n)O(n)

#include <bits/stdc++.h>
using namespace std;
int main() {
    int a, b;
    long long ans = 1;
    cin >> a >> b;
    for (int i = 0; i < b; ++i) {
        ans *= a;
    }
    cout << ans << endl;
    return 0;
}

快速幂算法

当我们计算 aba^b 次方的时候可以利用 ab=(aa)b/2a^b = ({a*a})^{b/2}

这样每次我们只需要把指数 a 减少一半,底数 b 变为原来的平方即可大大减少循环的次数。

当指数为偶数时,每次把指数 b 缩小为原来的一半,底数 a 变为原来的平方。

84=(88)4/2=642=(6464)2/2=40961=4096 8^4 = ({8 * 8})^{4/2} = 64^2 = ({64 * 64})^{2/2} = 4096^1 = 4096

当指数为奇数时,我们可以指数 b 减一,结果乘以底数 a 。再按照偶数的规则进行运算即可。

85=848=(88)4/28=6428=(6464)2/28=409618=409688^5 = 8^4 * 8 = ({8 * 8})^{4/2} * 8 = 64^2 * 8 = ({64 * 64})^{2/2} * 8 = 4096^1 * 8 = 4096 * 8

这样快速幂算法的时间复杂度为:log2blog_2b

普通代码

我们每次循环,根据指数 b 为奇数还是偶数分别进行计算。

当输入的指数 b 为偶数时,每次循环指数 b 变为原来的一半,偶数除以 2 仍为偶数。 这样指数 b 在最后一次循环时会变为 1 ,此时即可算出结果。

当输入的指数 b 为奇数时,每次循环指数 b 减一。 这样指数 b 变成了偶数,随后即可按照偶数规则进行计算。

  1. 当前指数 b 为奇数,把指数 b-- ,结果 ans *= a
  2. 当前指数 b 为偶数,把指数 b /= 2,底数 a *= a
#include <bits/stdc++.h>
using namespace std;
int main() {
    int a, b;
    long long ans = 1;
    cin >> a >> b;
    while (b) {
        if (b % 2 == 1) {
            b--; ans *= a;
        } else {
            b /= 2; a *= a;
        }
    }
    cout << ans << endl;
    return 0;
}

优化代码

上述代码可以进行优化

  1. 我们可以使用b & 1来判断该数字的奇偶性,此代码可以获指数 b 二进制的最后一位,根据二进数的表示规则,当b & 1 = 1时,该数为奇数,当b & 1 = 0时,该数为偶数。
  2. 利用b >> 1代替b / 2
  3. 当指数 b 为奇数时,当减一后可以直接按照偶数规则进行运算。因此,每次循环我们可以直接右移一位即可,可以减少一次循环次数。
#include <bits/stdc++.h>
using namespace std;
int main() {
    int a, b;
    long long ans = 1;
    cin >> a >> b;
    while (b) {
        if (b & 1) {
            b--; ans *= a;
        }
        b >>= 1; a *= a;
    }
    cout << ans << endl;
    return 0;
}

相关题目

题目 Pow(x, n)

链接:leetcode 50. Pow(x, n)

此题需注意 n 的取值范围为:231<=n<=2311-2^{31} <= n <= 2^{31}-1,因此需要注意 n 为负数的情况。

当 n 为负数时,22=(22)1=1/222^{-2} = ({2^2})^{-1} = 1/{2^2},因此当我们计算负数时,可以先把指数 b 转为正数,再用 1 除以结果,即可得到最终结果。

注意:当n = INT_MIN时,-n 会越界,因此我们先把 n 转为long long类型,再转为负数。

class Solution {
public:
    double myPow(double x, int n) {
        long long power = n >= 0 ? n : -(long long) n;
        double ans = 1.0;

        while (power != 0) {
            if (power & 1) {
                ans = ans * x;
            }
            power >>= 1; // equal to power /= 2;
            x = x * x;
        }
        return n >= 0 ? ans : 1.0 / ans;
    }
};

题目 a^b

链接:AcWing 89. a^b

此道题目只需要返回 mod pmod\ p 后的结果。如果得到结果后再 mod pmod\ p ,如果数据过大则会导致越界。

因此可以利用 (a * b * c) % p = ((a % p) * (b % p) * (c % p)) % p 来计算。

#include <bits/stdc++.h>
using namespace std;
int main() {
    int a, b, p, ans = 1;
    cin >> a >> b >> p;
    while (b) {
        if (b & 1) {
            --b; ans = (long long) ans * a % p;
        }
        b >>= 1; a = (long long) a * a % p;
    }
    cout << ans % p<< endl;
    return 0;
}

题目 64位整数乘法

链接:AcWing 90. 64位整数乘法 我们可以利用快速幂的思路来求解大整数的乘法。

  1. 当 b 为奇数的时候,我们可以减一,结果加上 a 。
  2. 当 b 为偶数的时候,我们把 a 扩大2倍,b 变为原来的一倍。

注意:当 a 为奇数的时候,我们可以不减一,而是利用C++除法的向下取整来代替减一。例如:5/2=25 / 2 = 2 ,相当于先减去一,再进行除以 2 ,因此此步只需除以 2 即可。

#include <bits/stdc++.h>
using namespace std;
unsigned long long a, b, p, ans;
int main() {
    cin >> a >> b >> p;
    while (b) {
        if (b & 1) ans = (ans + a) % p;
        a = a * 2 % p;
        b >>= 1;
    }
    cout << ans << endl;
    return 0;
}