数据结构与算法学习笔记----快速幂
@@ author: 明月清了个风
@@ first publish time: 2025.1.2
ps⭐️快速幂的两道模版题,快速幂,乘法逆元,费马小定理
Acwing 875. 快速幂
原题链接\]([875. 快速幂 - AcWing题库](https://www.acwing.com/problem/content/877/))
给定 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 aibimodpi的值
#### 输入格式
第一行包含整数 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 aibimodpi的值。
每个结果占一行。
#### 数据范围
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+⋯+2xkmodp=a2x1a2x2⋯a2xkmodp=(a2x1modp)⋅(a2x2modp)⋯(a2xkmodp)
在代码中,我们也无需对 k k k的二进制分解及 a x a\^x ax进行预处理,只需要边运算边处理就行,具体看下面代码吧,代码还是很清晰的。
#### 代码
```cpp
#include
#include
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次方就是a
k >>= 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题库](https://www.acwing.com/problem/content/878/))
给定 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的倍数,只有这样的情况他们才不互质。
#### 代码
```cpp
#include
#include
#include
#include
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;
}
```