數據結構與算法學習筆記----快速冪
@@ author: 明月清了個風
@@ first publish time: 2025.1.2ps??快速冪的兩道模版題,快速冪,乘法逆元,費馬小定理
Acwing 875. 快速冪
[原題鏈接](875. 快速冪 - AcWing題庫)
給定 n n n組 a i , b i , p i a_i,b_i,p_i ai?,bi?,pi?,對于每組數據,求出 a i b i m o d p i a_i^{b_i} \; mod \; p_i aibi??modpi?的值
輸入格式
第一行包含整數 n n n,
接下來 n n n行,每行包含三個整數 a i , b i , p i a_i,b_i,p_i ai?,bi?,pi?。
輸出格式
對于每組數據,輸出一個結果,表示 a i b i m o d p i a_i^{b_i} \; mod \; p_i aibi??modpi?的值。
每個結果占一行。
數據范圍
1 ≤ n ≤ 100000 1 \le n \le 100000 1≤n≤100000,
1 ≤ a i , b i , p i ≤ 2 × 1 0 9 1 \le a_i,b_i,p_i \le 2\times 10^9 1≤ai?,bi?,pi?≤2×109、
思路
快速冪是一種高效的計算整數冪的算法,適用于大數的冪運算,比如這題的數據范圍, a i , b i , p i a_i,b_i,p_i ai?,bi?,pi?都可以到億的級別,通過快速冪可以將暴力運算的 O ( n ) O(n) O(n)時間復雜度優化到 O ( log ? n ) O(\log n) O(logn)級別, n n n是冪的范圍。
核心思想:將指數 b b b通過二進制分解,從而達到 log ? b \log b logb的運算量,也就是
a b m o d p = a ( 2 x 1 ) + 2 x 2 + ? + 2 x k m o d p = a 2 x 1 a 2 x 2 ? a 2 x k m o d p = ( a 2 x 1 m o d p ) ? ( a 2 x 2 m o d p ) ? ( a 2 x k m o d p ) \begin{align*} a^b \; mod \; p & = a^{(2^{x_1}) + 2^{x_2} + \cdots + 2^{x_k}} \; mod \; p \\ & = a^{2 ^ {x_1}} a^{2 ^ {x_2}} \cdots a^{2^{x_k}} \; mod \; p \\ & = (a^{2^{x_1}}mod \;p)\cdot(a^{2^{x_2}}mod \;p) \cdots(a^{2^{x_k}}mod \;p) \\ \end{align*} abmodp?=a(2x1?)+2x2?+?+2xk?modp=a2x1?a2x2??a2xk?modp=(a2x1?modp)?(a2x2?modp)?(a2xk?modp)?
在代碼中,我們也無需對 k k k的二進制分解及 a x a^x ax進行預處理,只需要邊運算邊處理就行,具體看下面代碼吧,代碼還是很清晰的。
代碼
#include <iostream>
#include <cstring>using namespace std;typedef long long LL;int qmi(int a, int k, int p)
{int res = 1;while(k) {if(k & 1) res = (LL) res * a % p; // 從k的二進制表示最低位開始,也就是2的0次方,此時a的2的0次方就是ak >>= 1; // 每次右移一位a = (LL) a * a % p; // 每次將a平方,因為a上面是2的次方,每次提高一位相當于乘一個a的2的0次方,就是a}return res;
}int main()
{int n;cin >> n;while(n --){int a, b, p;cin >> a >> b >> p;cout << qmi(a, b, p) << endl;}return 0;
}
Acwing 876. 快速冪求逆元
[原題鏈接](876. 快速冪求逆元 - AcWing題庫)
給定 n n n組 a i , p i a_i,p_i ai?,pi?,其中 p i p_i pi?是質數,求 a i a_i ai?模 p i p_i pi?的乘法逆元,若逆元不存在則輸出impossible
。
注意:請返回 0 ~ p ? 1 0 \sim p - 1 0~p?1之間的逆元。
乘法逆元的定義:
若整數 b , m b,m b,m互質,并且對于任意的整數 a a a,如果滿足 b ∣ a b|a b∣a,則存在一個整數 x x x,使得 a b ≡ a × x ( m o d m ) \frac{a}{b} \equiv a \times x (mod \; m) ba?≡a×x(modm),則稱 x x x為 b b b模 m m m的乘法逆元,記為 b ? 1 ( m o d m ) b^{-1}(mod \; m) b?1(modm)。
b b b存在乘法逆元的充要條件是 b b b與模數 m m m互質,當模數 m m m為質數時, b m ? 2 b^{m - 2} bm?2即為 b b b的乘法逆元。
輸入格式
第一行包含整數 n n n,
接下來 n n n行,每行包含一個數組 a i , p i a_i,p_i ai?,pi?,數據保證 p i p_i pi?是質數。
輸出格式
輸出共 n n n行,每組數據輸出一個結果,每個結果占一行。
若 a i a_i ai?模 p i p_i pi?的乘法逆元存在,則輸出一個整數表示逆元,否則輸出impossbile
。
數據范圍
1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1≤n≤105,
1 ≤ a i , p i ≤ 2 ? 1 0 9 1 \le a_i, p_i \le 2 * 10^9 1≤ai?,pi?≤2?109
思路
主要的難點是定義比較繞,我們可以對定義的式子進行一些變形
a b ≡ a × x ( m o d m ) (1) \frac{a}{b} \equiv a \times x (mod \; m) \tag{1} ba?≡a×x(modm)(1)
在式(1)的兩邊同乘 b b b,我們可以得到式(2)
a ≡ b × a × x ( m o d m ) (2) a \equiv b \times a \times x (mod \; m) \tag{2} a≡b×a×x(modm)(2)
兩邊再同時除 a a a可以得到
1 ≡ b × x ( m o d m ) (3) 1 \equiv b \times x (mod \; m) \tag{3} 1≡b×x(modm)(3)
因此我們要求的就是一個 x x x,可以使 b × x ≡ 1 ( m o d m ) b \times x \equiv 1 (mod \; m) b×x≡1(modm)
還有一個注意點是 b b b存在乘法逆元的充要條件是模數 m m m與 b b b互質,題目中給出了條件模數 p i p_i pi?保證了是質數,也就是保證了這個條件。
這里需要補充一個額外的定理:費馬小定理
當 m m m是一個質數,對于任意整數 a a a( a a a不被 m m m整除),有 a m ? 1 ≡ 1 ( m o d m ) a^{m- 1} \equiv 1 (mod \; m) am?1≡1(modm)
那么對費馬小定理的這個公式進行變形,拆分次方可得 a ? a m ? 2 ≡ 1 ( m o d m ) a \cdot a^{m - 2} \equiv 1 \; (mod \; m) a?am?2≡1(modm),我們就能直接得到 a a a的乘法逆元是 a m ? 2 a^{m - 2} am?2。
因此問題轉換為求 a m ? 2 a^{m - 2} am?2,也就是應用上面的快速冪。
需要注意的是題目雖然保證了 p p p是一個質數,但是卻被沒有保證我們的充要條件,也就是 a i a_i ai?與 p i p_i pi?互質,因此需要判斷 a i a_i ai?是否是 p i p_i pi?的倍數,只有這樣的情況他們才不互質。
代碼
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;typedef long long LL;int qmi(int a, int k, int p)
{int res = 1;while(k){if(k & 1) res = (LL) res * a % p;k >>= 1;a = (LL) a * a % p;}return res;
}int main()
{int n;cin >> n;while(n --){int a, p;cin >> a >> p;if(a % p == 0) puts("impossible");else cout << qmi(a, p - 2, p) << endl;}return 0;
}