数论 | 快速幂、矩阵快速幂、快速乘 - 详细讲解

1、快速幂

快速幂:快速计算某个数的幂次( a n a^n )html

快速幂时间复杂度为 O ( l o g n ) O(logn) ,相比朴素的 O ( n ) O(n) 快了不少ios

它的基本原理是把 n 转换为二进制,如 23 = 1 + 2 + 4 + 16 = 2 0 + 2 1 + 2 2 + 2 4 = ( 10111 ) 2 23 = 1 + 2 + 4 +16 = 2^0 + 2 ^ 1 + 2^2 + 2^4=(10111)_2 web

核心:反复平方法,即 不断把底数平方
在这里插入图片描述算法

快速幂模版(迭代,非递归)

int fastpow(int a, int k, int p) {
    int res = 1;
    while (k) {
        if (k & 1) res = res * 1ll * a % p;
        a = a * 1ll * a % p; 
        k >>= 1;
    }
    
    return res;
}

快速幂模版(递归)

计算 a 的 k 次,若是 k 为偶数(不为0),则先计算 a n / 2 a^{n/2} ,而后平方;若是 k 是奇数,则先计算 a n 1 a^{n-1} ,而后在乘以 a;递归的出口是 a 0 = 1 a^0=1 编程

int fastpow(int a, int k, int p) {
    if (k == 0) return 1 % p;
    
    if (k & 1) return fastpow(a, k - 1, p) * 1ll * a % p;
    else {
        // 必须用临时遍历tmp存,不然算法退回到O(n)
        int tmp = fastpow(a, k / 2, p); 
        return tmp * 1ll * tmp % p;
    }
}

说明:递归快速幂思路比较简单,但要注意 a n / 2 a^{n/2} 须要用临时遍历存下来,不然算两次,会让算法退回到 O ( n ) O(n) 复杂度app

注:快速幂因为过于简单,一般做为某个复杂算法的中间一步,好比欧拉公式,欧几里得算法svg


AcWing 875. 快速幂

875. 快速幂函数

LeetCode 50. Pow(x, n)(快速幂 C++)

LeetCode 题解 | 50. Pow(x, n)(快速幂 C++)学习

LeetCode 372. 超级次方

LeetCode 372. 超级次方spa


2、矩阵快速幂

数学问题:矩阵n次方的七种求法的概括——在本科数学时学过里面的几种方法

在这里插入图片描述
在这里插入图片描述

上面说的是数学方法,在计算机编程中常常用快速幂来求矩阵的 n 次幂

矩阵快速幂适合于求递推式 f ( n ) = a f ( n 1 ) + b f ( n 2 ) f(n) = af(n - 1) + bf(n-2) ,加速递推公式

特别是当 n 很大时,它能在 O ( l o g n ) O(logn) 级别的时间复杂度内求出第 n 项,如求斐波那契数列的第1e9项

( f ( n + 1 ) f ( n ) ) = ( a b 1 0 ) ( f ( n ) f ( n 1 ) ) = ( a b 1 0 ) n 1 ( f ( 2 ) f ( 1 ) ) \left( \begin{array}{c} f(n+1) \\ f(n) \end{array} \right) = \left( \begin{array}{c c} a &b \\ 1 & 0 \\ \end{array} \right) \left( \begin{array}{c} f(n) \\ f(n - 1) \end{array} \right) = \left( \begin{array}{c c} a &b \\ 1 & 0 \\ \end{array} \right) ^{n-1} \left( \begin{array}{c} f(2) \\ f(1) \end{array} \right)

显然当咱们算出 ( a b 1 0 ) n 1 \left( \begin{array}{c c} a &b \\ 1 & 0 \\ \end{array} \right)^{n-1} 后, f ( n + 1 ) f ( n ) f(n + 1) \text{和} f(n) 的值就都算出来了(这算法是否是很牛逼!)


咱们用一个结构体来封装矩阵,这样能够简化不少代码,让程序看上去更舒服

struct mat{
    int m[N][N];
    mat() { // 用构造函数初始化成 单位矩阵E
        memset(m, 0, sizeof m);
        for (int i = 0; i < N; i ++) m[i][i] = 1;
    }
}

矩阵快速幂函数(和普通快速幂相似)

mat fastpow(mat a, int k) { // 矩阵快速幂
    mat res;
    while (k) {
        if (k & 1) res = multi(res, a);
        a = multi(a, a); 
        k >>= 1; 
    }
    return res;
}

矩阵快速幂模版

结构体(存矩阵) + 矩阵乘法 + 矩阵快速幂

#include <iostream>

using namespace std;

const int N = 100, mod = 1e6;
int n, k;

struct mat {
    int m[N][N];
    mat() { // 用构造函数初始化成 单位矩阵E
        memset(m, 0, sizeof m);
        for (int i = 0; i < N; i++)
            m[i][i] = 1;
    }
};

// 矩阵乘法
mat multi(mat a, mat b) { 
    mat c;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            c.m[i][j] = 0;
            for (int k = 0; k < n; k++) {
                c.m[i][j] += a.m[i][k] * b.m[k][j]; // 累加
            }
            c.m[i][j] %= mod;
        }
    }
    return c;
}

// 矩阵快速幂(核心)
mat fastpow(mat a, int k) { 
    mat res;
    while (k) {
        if (k & 1) res = multi(res, a);
        a = multi(a, a);
        k >>= 1;
    }
    return res;
}

int main() {
    cin >> n >> k; // 输入矩阵阶数 n 和幂次 k

    mat a, res;
    // 输入矩阵
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> a.m[i][j];
        }
    }

    res = fastpow(a, k);

    // 输出结果
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cout << res.m[i][j] << " ";
        }
        cout << endl;
    }
}

输入

3 10
-1 1 0
-4 3 0
1 0 2

输出

-19 10 0 
-40 21 0 
-1003 1013 1024

将 n=10 代入通项 A n A^n ,能够知道咱们的代码是正确的

说明:通常题目给的数据会很大,因此都会取 mod

例题:求斐波那契数列的第 1e9 项

POJ 题解 | 3070.Fibonacci 求斐波那契数列的第 n 项(矩阵快速幂 C++)

例题: S = A + A 2 + A 3 + + A k S = A + A^2 + A^3 + … + A^k

POJ 3233.Matrix Power Series

分析能够获得
k为偶数: s u m ( k ) = ( E + A k / 2 ) ( A + A 2 + + A k / 2 ) = ( E + A k / 2 ) × s u m ( k / 2 ) sum(k) = (E+A^{k/2}) *( A+A^2+……+A^{k/2}) \\ = (E+A^{k/2}) \times sum(k/2)
k为奇数: s u m ( k ) = ( E + A ( k 1 ) / 2 ) s u m ( k / 2 ) + A k sum(k) = (E+A^{(k-1)/2}) * sum(k/2) + A^k

A k A^k 能够用矩阵快速幂来求,而后再递归 sum 便可

POJ 题解 | 3233. Matrix Power Series(矩阵快速幂 C++)

@例题:商汤科技笔试第三题

关键在于构造出递推的矩阵

能够理解为选定一组基

矩阵快速幂 —— 商汤笔试第三题

@数位问题

在全部的 n 位正数中,有多少个数含有偶数个3,有多少个数含有奇数个3?
注: n 1 0 9 n \leq10^9
在这里插入图片描述

参考:https://zhuanlan.zhihu.com/p/137677246

3、快速乘

因为计算机底层设计的缘由,作加法每每比乘法快的多,所以将乘法转换为加法计算将会大大提升乘法运算的速度

除此以外,当咱们计算 a b a*b%mod 的时候,每每较大的数计算 a b a*b 会超出 long long int 的范围,这个时候使用快速乘法方法也能解决上述问题。

快速乘法的原理:利用乘法分配率来将 a b a*b 转化为多个式子相加的形式求解

例如:
20 × 14 20 × ( 1110 ) 2 = ( 20 × 2 3 ) × 1 + ( 20 × 2 2 ) × 1 + ( 20 × 2 1 ) × + ( 20 × 2 0 ) × 0 = 160 + 80 + 40 = 280 \begin{aligned} 20 \times 14 &= 20\times (1110)2 \\ &= (20 \times 2^3) \times 1 + (20 \times 2^2) \times 1+(20 \times 2^1) \times 1+(20 \times 2^0) \times 0 \\&= 160+80+40 = 280 \end{aligned}

typedef long long ll;
ll qmm(ll a, ll b, ll mod) {
    ll ans = 0;
    while (b) {
        if (b & 1) ans = (ans + a) % mod;
        a <<= 1; // 20*1->20*2->20*4->20*8
        b >>= 1;
    }
    return ans;
}

参考资料

[1] 数论之矩阵快速幂
[2] 算法学习笔记(4):快速幂 - 知乎
[3] 算法竞赛模板 快速乘与快速幂 - Kannyi - 博客园
[4] 二分幂,快速幂,矩阵快速幂,快速乘