Skip to content

快速幂

快速幂分为递归式与非递归式,其核心递推式如下:

$$ a_n = \begin{cases} 1 & n = 0 \\ a^{\frac{n}{2}} \cdot a^{\frac{n}{2}} & n \bmod 2 = 0 \\ a \cdot a^{\frac{n-1}{2}} \cdot a^{\frac{n-1}{2}} & n \bmod 2 \neq 0 \end{cases} $$

递归式快速幂(直白)

1
2
3
4
5
6
7
8
9
long long qpow(int a, int n)
{
    if (n == 0) return 1;               // 结束递归条件
    long long res = qpow(a, n / 2);
    if (n % 2)                          // n为奇数
        return res * res * a;           // 指数减一后对半
    else                                // n为偶数
        return res * res;               // 指数对半
}

非递归式快速幂(推荐)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
long long qpow(int a, int n)
{
    long long res = 1;                  // 设定初始值
    while (n > 0)
    {
        if(n % 2 == 1)                  // if (n & 1)
            res *= a;                   // 取出一个底数相乘
        a *= a;                         // 将a平方
        n /= 2;                         // n >>= 1 指数对半(n是奇数时丢掉的余数在 res *= a 这里已经进行处理)
    }
    return res;
}

可以将高精度和快速幂结合在一起,实现大数的快速幂。

矩阵快速幂

进行快速幂计算的对象是矩阵,首先初始定义一个(int)矩阵:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
struct Matrix{
    vector<vector<int>> mat;                                    // 矩阵
    int rows, cols;                                             // 行列数据

    // 构造函数,使得在创建Matrix的时候可以像STL容器一样指定行列
    // rows(rows), cols(cols) 是初始化列表,用于右侧的初始化(两个rows/cols实质不同): mat(rows, vector<int>(cols))
    Matrix(int rows, int cols) : rows(rows), cols(cols), mat(rows, vector<int>(cols)) {}

    vector<int>& operator[](int index) {                        // 重载[]运算符进行对矩阵元素的访问
        return mat[index];
    }

    // 如果定义了一个 const Matrix,必须要定义一个const版本的[]重载 (对*的重载就用到了const版本的[]重载)
    const vector<int>& operator[](int index) const {
        return mat[index];
    }

    void print() const {                                        // 打印矩阵操作
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                cout << mat[i][j] << " ";
            }
            cout << endl;
        }
    }

};

// 重载*运算符实现矩阵乘法运算
Matrix operator*(const Matrix& a, const Matrix& b) {
    Matrix result(a.rows, b.cols);
    // 这里忽略了行列合法性检查
    for (int i = 0; i < a.rows; i++) {
        for (int j = 0; j < b.cols; j++) {
            int sum = 0;
            for (int k = 0; k < a.cols; k++) {
                sum += a[i][k] * b[k][j];           // 进行点积计算
            }
            result[i][j] = sum;
        }
    }
    return result;
}

// 使用例:
Matrix A(2,3);      // 定义一个 2 * 3 的矩阵;
Matrix B(3,2);
A[0][0] = 1;        // 设定值
Matrix C = A * B;   // 矩阵乘法计算
C.print();          // 打印操作

然后采用非成员函数的方式定义 pow() 函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Matrix matrix_pow(Matrix base, long long exp) {

    if (base.rows != base.cols) {
        // error check
    }

    int n = base.rows;
    // 初始化单位矩阵
    Matrix result(n,n);
    for(int i = 0; i < n; i++) result[i][i] = 1;

    // 快速幂算法
    while (exp > 0) {
        if (exp & 1) {                     // 如果指数是奇数
            result = result * base;
        }
        base = base * base;  // 底数平方
        exp >>= 1;                         // 指数除以2
    }

    return result;
}

实现了矩阵快速幂

应用:计算斐波那契数列

学过线性代数之后不难计算,对于斐波那契数列 $F_n$ 有:

$$ \begin{aligned}\begin{pmatrix}F_n \\ F_{n-1}\end{pmatrix}=\begin{pmatrix}1 & 1 \\1 &0 \end{pmatrix}\begin{pmatrix}F_{n-1} \\ F_{n-2}\end{pmatrix},\ n\geq 3\end{aligned} $$

进一步得到

$$ \begin{aligned}\begin{pmatrix}F_n \\ F_{n-1}\end{pmatrix}=\begin{pmatrix}1 & 1 \\1 & 0\end{pmatrix}^{n-1} \begin{pmatrix}F_{1} \\ F_{0}\end{pmatrix},\ n\geq 3\end{aligned} $$

对于 $\begin{pmatrix}1 & 1 \\1 & 0\end{pmatrix}^{n-1}$ 的计算,我们采用矩阵快速幂,然后进行一轮矩阵运算就可以得到单个 $F_n$ 的计算结果,时间复杂度 $O(\log n)$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Luogu P1962 斐波那契数列
// 记得在矩阵乘法重载时 % MOD
void solve(){
    ll n; cin >> n; 
    if(n <= 1){
        cout << n; return;
    }

    Matrix A(2,2);
    A[0][0] = A[0][1] = A[1][0] = 1, A[1][1] = 0;
    A = matrix_pow(A, n-1);

    Matrix B(2,1);
    B[0][0] = 1; B[1][0] = 0;

    Matrix C = A * B;

    cout << C[0][0];
}

对于广义的斐波那契数列:$a_n = p \times a_{n-1} + q \times a_{n-2}$ 也有: $$ \begin{aligned}\begin{pmatrix}a_n \\ a_{n-1}\end{pmatrix}=\begin{pmatrix}p & q \\1 &0 \end{pmatrix}\begin{pmatrix}a_{n-1} \\ a_{n-2}\end{pmatrix},\ n\geq 3\end{aligned} $$ 也有一道对应的 P1349 广义斐波那契数列 - 洛谷