快速幂


  • 引入

    1、假设我们要计算 an 的值,最朴素的做法是循环 n 次,每次在结果上乘以 a。显然这种做法的时间复杂度为 O(n),但当n 非常大时这种做法的效率仍然较低,而快速幂可以解决这个问题
    2、我们先从一个特例引入:当n 是 2 的幂时,例如求 a64 的值,我们可以按照下表的方式计算。每次将上一次循环得到的结果自乘,就得到了指数下一级 2 的幂的结果,以此类推,便很快就能得到指数所求 2 的幂的结果
    3、而对于任意的 n,例如求 a105 的值,我们可以将指数 n 拆分成多个 2 的幂之和,例如 a105 = a1 + 8 + 32 + 64 = a1 * a8 * a32 * a64,而单独计算这些幂便十分简单了,因此最终只需要将需要的 2 的幂的结果相乘即可
    4、因此,快速幂又称二进制取幂(平方法),其英文为Binary Exponentiation,其时间复杂度仅需 O(log n) 对数级

    第 i 次循环 计算的值 第 i 次循环 计算的值
    1 a1 * a1 = a2 4 a8 * a8 = a16
    2 a2 * a2 = a4 5 a16 * a16 = a32
    3 a4 * a4 = a8 6 a32 * a32 = a64
  • 代码实现

    1、这个算法的关键,在于如何将 n 分解为多个 2 的幂之和。如上例,105二进制1101001,我们发现其二进制 1 的位置的权重便是拆开后所需的 2 的幂(最右起第一个 1 权重为 20,第二个 1 权重为 23,第三个 1 权重为 25,第四个 1 权重为 26)
    2、因此,我们可以通过这种方式完成拆分代码实现模板如下,其中部分细节解释如下图(以计算 7105 为例)

    // 快速幂,计算 a 的 n 次方
    unsigned long long BinExp(int a, int n)
    {
        unsigned long long result = 1;
    
        // 拆分指数 n,只要 n 不为 0 便继续循环
        while (n)
        {
            // 如果 n 的二进制最后一位是 1
            if (n & 1)
                // result 乘上 a 的 `2的当前位权值次方` 的结果
                result *= a;
    
            // a 自乘,a 变成下一级 a 的 `2的幂次方` 的结果,a^2 -> a^4 -> a^8
            a *= a;
            // 位运算右移一位,下一次循环判断下一位二进制
            n >>= 1;
        }
    
        return result;
    }

幂取模


  • 引入

    1、计算 an % m 的值,其中a、n、m 都是正整数,这种运算方式称为幂取模运算
    2、注意:取模时乘法运算同样适用的,即(a * b) % m = ((a % m) * (b % m)) % m
    3、因此,我们只需要在快速幂适当位置进行取模运算,就可以解决直接运算会超出范围的问题

  • 代码实现

    #include <iostream>
    using namespace std;
    
    // 幂取模
    unsigned long long ModExp(int a, int n, int m)
    {
        unsigned long long result = 1;
        a %= m; // 对第一次的乘数 a 取模
    
        while (n)
        {
            if (n & 1)
                result = (result * a) % m; // 对结果取模,结果又是下一次运算的乘数之一
    
            a = (a * a) % m; // 对乘数 a 取模
            n >>= 1;
        }
    
        return result;
    }
    
    int main()
    {
        // 计算 a^n % m
        int a, n, m;
        cin >> a >> n >> m;
    
        unsigned long long ans = ModExp(a, n, m);
        cout << ans;
        return 0;
    }

矩阵快速幂


  • 引入

    1、斐波那契数列0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...,计算数列第 n 项的值
    2、将数列的第 n+1 项第 n 项列向量表示(如下图),其表示为(Fn+1, Fn),发现其等于矩阵 [[1, 1], [1, 0]] 乘以列向量 (Fn, Fn-1),继续向下展开推导,最终发现其等于矩阵 [[1, 1], [1, 0]]n 乘以列向量 (F1, F0)
    3、因此,我们可以通过计算矩阵快速幂得到答案,其也只需要在快速幂上进行部分修改即可

  • 代码实现

    // 矩阵快速幂伪代码,只代表思路,不能直接运行
    // 其中 A,result,I 都是矩阵
    MAT MATExp(MAT A, int n)
    {
        // 将 result 初始化为单位矩阵 I
        MAT result = I;
    
        while (n)
        {
            if (n & 1)
                result *= A;
    
            A *= A;
            n >>= 1;
        }
    
        return result;
    }

页底评论