數論 | 快速冪、矩陣快速冪、快速乘

一、快速冪

快速冪:快速計算某個數的冪次( ana^n )

快速冪時間複雜度爲 O(logn)O(logn),相比樸素的 O(n)O(n)快了很多

它的基本原理是把 n 轉換爲二進制,如 23=1+2+4+16=20+21+22+24=(10111)223 = 1 + 2 + 4 +16 = 2^0 + 2 ^ 1 + 2^2 + 2^4=(10111)_2

核心:反覆平方法,即 不斷把底數平方
在這裏插入圖片描述

快速冪模版(迭代,非遞歸)

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),則先計算 an/2a^{n/2},然後平方;如果 k 是奇數,則先計算 an1a^{n-1},然後在乘以 a;遞歸的出口是 a0=1a^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;
    }
}

說明:遞歸快速冪思路比較簡單,但要注意 an/2a^{n/2}需要用臨時遍歷存下來,否則算兩次,會讓算法退回到 O(n)O(n)複雜度

注:快速冪由於過於簡單,通常作爲某個複雜算法的中間一步,比如歐拉公式,歐幾里得算法


AcWing 875. 快速冪

875. 快速冪

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

LeetCode 題解 | 50. Pow(x, n)(快速冪 C++)

LeetCode 372. 超級次方

原題鏈接

LeetCode 題解 | 372. 超級次方(快速冪 C++)

AcWing 876. 快速冪求逆元(乘法逆元)

乘法逆元定義: bx1(modp)bx \equiv 1 (\bmod p),當 b 和 p 互質時,方程的解 x 稱爲 b 模 p 的逆元

說明:乘法逆元不唯一,x+kpx + kp 是通解

原題鏈接

求同餘方程 ax1(modp)ax\equiv 1(\bmod p),即求 a 關於 模 p 的逆元

這裏模 p 的 p 是質數,是特殊情況,所以可以用快速冪來求,更一般的做法是用擴展歐幾里得算法,構造出 x

/* ===== 解題思路 =====
1. p是質數,當 a 和 p 互質時,a 模 p 的乘法逆元是 a^(p-2) % p (在0~p-1上的逆元)
2. 用快速冪求 a^(p-2) % p
*/

#include <iostream>

using namespace std;

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;
}

int main() {
    int n;
    cin >> n;
    
    while (n --) {
        int a, p;
        cin >> a >> p;
        if (a % p != 0) { // a 和 p 互質
            cout << fastpow(a, p - 2, p) << endl;
        } else {
            cout << "impossible" << endl;
        }
    }
    
    return 0;
}

二、矩陣快速冪

數學問題:矩陣n次方的七種求法的歸納——在本科數學時學過裏面的幾種方法

在這裏插入圖片描述
在這裏插入圖片描述

上面說的是數學方法,在計算機編程中經常用快速冪來求矩陣的 n 次冪

矩陣快速冪適合於求遞推式 f(n)=af(n1)+bf(n2)f(n) = af(n - 1) + bf(n-2),加速遞推公式

特別是當 n 很大時,它能在 O(logn)O(logn)級別的時間複雜度內求出第 n 項,如求斐波那契數列的第1e9項

(f(n+1)f(n))=(ab10)(f(n)f(n1))=(ab10)n1(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)

顯然當我們算出 (ab10)n1\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 代入通項 AnA^n,可以知道我們的代碼是正確的

說明:一般題目給的數據會很大,所以都會取 mod

例題:求斐波那契數列的第 1e9 項

POJ 題解 | 3070.Fibonacci 求斐波那契數列的第 n 項(矩陣快速冪 C++)

例題:S=A+A2+A3++AkS = A + A^2 + A^3 + … + A^k

POJ 3233.Matrix Power Series

分析可以得到
k爲偶數:sum(k)=(E+Ak/2)(A+A2++Ak/2)=(E+Ak/2)×sum(k/2)sum(k) = (E+A^{k/2}) *( A+A^2+……+A^{k/2}) \\ = (E+A^{k/2}) \times sum(k/2)
k爲奇數:sum(k)=(E+A(k1)/2)sum(k/2)+Aksum(k) = (E+A^{(k-1)/2}) * sum(k/2) + A^k

AkA^k可以用矩陣快速冪來求,然後再遞歸 sum 即可

POJ 題解 | 3233. Matrix Power Series(矩陣快速冪 C++)

@例題:商湯科技筆試第三題

關鍵在於構造出遞推的矩陣

可以理解爲選定一組基

矩陣快速冪 —— 商湯筆試第三題

@數位問題

在所有的 n 位正數中,有多少個數含有偶數個3,有多少個數含有奇數個3?
注: n109n \leq10^9
在這裏插入圖片描述

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

三、快速乘

由於計算機底層設計的原因,做加法往往比乘法快的多,因此將乘法轉換爲加法計算將會大大提高乘法運算的速度

除此之外,當我們計算 aba*b%mod 的時候,往往較大的數計算 aba*b 會超出 long long int 的範圍,這個時候使用快速乘法方法也能解決上述問題。

快速乘法的原理:利用乘法分配率來將aba*b轉化爲多個式子相加的形式求解

例如:
20×1420×(1110)2=(20×23)×1+(20×22)×1+(20×21)×+(20×20)×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] 二分冪,快速冪,矩陣快速冪,快速乘

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章