快速幂与扩展欧拉定理
把这两个知识点放一起是因为扩展欧拉定理需要用到快速幂。
快速幂
定义
快速幂,即很快的做幂运算,并且在求幂的同时支持取模操作。但这个算法究竟是怎么实现的,我们来探究一下。
快速幂的原理
假设我们需要求 2 8 2^8 28 是多少?
最暴力的方法就是一个一个乘:
2 × 2 × ⋯ × 2 × 2 ⏟ 8 个 2 = 256 \underbrace{2\times 2\times \cdots \times 2\times 2}_{8 个 2}=256 8个2 2×2×⋯×2×2=256
这样的时间复杂度是: O ( n ) O(n) O(n)。当指数大时,这样的暴力一定 T 到飞起。
当然,聪明的你们(不是我)一定发现了一个简便的方法,那就是让底数不断平方,然后指数不断除以 2 2 2。即:
2 8 = 2 2 4 = 4 4 = 4 2 2 = 1 6 2 = 256 2^{8}=2^{2^4}=4^4=4^{2^2}=16^2=256 28=224=44=422=162=256
不难发现,这样求幂只运算了 3 3 3 次,是 log \log log 级别的。但如果指数不是 2 2 2 的倍数怎么办呢?我们再来看一个例子:
求 2 10 2^{10} 210 是多少?
我们还是先按之前的方法做:
2 10 = 2 2 5 = 4 5 2^{10}=2^{2^5}=4^5 210=225=45
现在我们发现指数变成了奇数,不能再除以 2 2 2 了。对于这种情况,我们可以这样处理:把一个底数单独拿出来,使得指数减一而成为偶数。即:
4 5 = 4 4 × 4 4^5=4^4\times 4 45=44×4
经过处理,我们就可以继续重复原来的步骤了:
4 4 × 4 = 1 6 2 × 4 = 256 × 4 = 1024 4^4\times 4=16^2\times 4=256\times 4=1024 44×4=162×4=256×4=1024
这两个步骤就是快速幂算法的主要流程。
具体实现:
我们可以定义变量 result
,记得一定要初始化为 1 1 1。然后如果指数变成了奇数,则令result
乘上底数,否则指数除以二,底数平方。
代码实现
由上面的两个步骤,我们可以写出基本的算法框架:
int ksm(int base , int x) {
int result = 1;
while(x) {
if(x % 2 == 1)
result *= base;
x /= 2;
base *= base;
}
return result;
}
但这还不是最快的算法。我们可以将取模运算以及除法运算改成位运算:
inline int ksm(int base , int x) {
int result = 1;
while(x) {
if(x & 1)
result *= base;
x >>= 1;
base *= base;
}
return;
}
欧拉定理与扩展欧拉定理
欧拉定理
定义
对于 a a a, m m m 两数,若 gcd ( a , m ) = 1 \gcd(a,m)=1 gcd(a,m)=1 则有: a φ ( m ) ≡ 1 ( m o d m ) a^{\varphi(m)}\equiv 1\pmod m aφ(m)≡1(modm)。
其实费马小定理就是欧拉定理的特殊情况,即当 m m m 为素数时。
欧拉定理的证明
设 A = { x 1 , x 2 , ⋯ , x φ ( m ) } A=\left\{x_1,x_2,\cdots,x_{\varphi(m)}\right\} A={x1,x2,⋯,xφ(m)} 是 1 − m 1-m 1−m 中与 m m m 互素的数的集合。
则这些数模 m m m 互不相同,且余数与 m m m 互素。
接下来,我们来证明集合 B = { a x 1 , a x 2 , ⋯ , a x φ ( m ) } B=\left\{ax_1,ax_2,\cdots,ax_{\varphi(m)}\right\} B={ax1,ax2,⋯,axφ(m)} 且 gcd ( a , m ) = 1 \gcd(a,m)=1 gcd(a,m)=1 也有这样的性质。
我们先证这些数模 m m m 互不相同:
考虑反证法:
若 a x i ≡ a x j ( m o d m ) ax_i\equiv ax_j\pmod m axi≡axj(modm) 且 i ≠ j i\neq j i=j。
则有 a x i − a x j ≡ 0 ( m o d m ) ax_i-ax_j\equiv 0\pmod m axi−axj≡0(modm)。
即 a ( x i − x j ) ≡ 0 ( m o d m ) a(x_i-x_j)\equiv 0\pmod m a(xi−xj)≡0(modm)。
因为 a a a 与 m m m 互素且 x i − x j ∤ m x_i-x_j\nmid m xi−xj∤m 所以这种情况不存在,得证。
接下来,我们证余数与 m m m 互素:
因为 a a a 与 m m m 互素,且 x i x_i xi 与 m m m 互素。
所以 a x i ax_i axi 与 m m m 互素。得证。
所以 B B B 集合在模 m m m 后便于 A A A 集合相等。可得:
∏ i = 1 φ ( m ) a x i ≡ ∏ i = 1 φ ( m ) x i ( m o d m ) \prod_{i=1}^{\varphi(m)} ax_i\equiv \prod_{i=1}^{\varphi(m)} x_i\pmod m i=1∏φ(m)axi≡i=1∏φ(m)xi(modm)
把 a a a 提出得:
a φ ( m ) ∏ i = 1 φ ( m ) x i ≡ ∏ i = 1 φ ( m ) x i ( m o d m ) a^{\varphi(m)}\prod_{i=1}^{\varphi(m)} x_i\equiv\prod_{i=1}^{\varphi(m)} x_i\pmod m aφ(m)i=1∏φ(m)xi≡i=1∏φ(m)xi(modm)
同时约掉 ∏ i = 1 φ ( m ) x i \prod_{i=1}^{\varphi(m)} x_i ∏i=1φ(m)xi 得:
a φ ( m ) ≡ 1 ( m o d m ) a^{\varphi(m)}\equiv 1\pmod m aφ(m)≡1(modm)
得证。
欧拉定理的推论与证明
若 a a a, m m m 互素,则有 a b ≡ a b m o d φ ( m ) ( m o d m ) a^b\equiv a^{b\bmod \varphi(m)}\pmod m ab≡abmodφ(m)(modm)。
下面给出证明:
设 b = k × φ ( m ) + t b=k\times\varphi(m)+t b=k×φ(m)+t,其中 t = b m o d φ ( m ) t=b\bmod \varphi(m) t=bmodφ(m)。
把 b b b 代入 a b a^b ab 得:
a b = a k × φ ( m ) + t = a k × φ ( m ) × a t = a φ ( m ) k × a t ≡ 1 k × a b m o d φ ( m ) ( m o d m ) ≡ a b m o d φ ( m ) ( m o d m ) \begin{aligned} a^b &= a^{k\times\varphi(m)+t}\\ &= a^{k\times \varphi(m)}\times a^t\\ &=a^{\varphi(m)^k}\times a^t\\ &\equiv 1^k\times a^{b\bmod \varphi(m)}\pmod m\\ &\equiv a^{b\bmod \varphi(m)}\pmod m\\ \end{aligned} ab=ak×φ(m)+t=ak×φ(m)×at=aφ(m)k×at≡1k×abmodφ(m)(modm)≡abmodφ(m)(modm)
得证。
扩展欧拉定理
在大部分情况下,我们都使用扩展欧拉定理,因为它可以处理的情况更多。
定义
对于 a b m o d m a^b\bmod m abmodm 这样一个式子,无论 a a a 与 m m m 互不互素都有:
a b m o d m ≡ { b ≥ φ ( m ) , a b m o d φ ( m ) + φ ( m ) ( m o d m ) b < φ ( m ) , a b ( m o d m ) a^b\bmod m\equiv \begin{cases} b\ge \varphi(m),a^{b\bmod \varphi(m)+\varphi(m)}\pmod m\\ b<\varphi(m),a^b\pmod m \end{cases} abmodm≡{b≥φ(m),abmodφ(m)+φ(m)(modm)b<φ(m),ab(modm)
对于第一种情况,即 b ≥ φ ( m ) b\ge\varphi(m) b≥φ(m) 时我们可以用公式化简来求解。
对于第二种情况,我们可以直接使用快速幂算出结果。
算法流程十分简单,我们只需要判断 b b b 与 φ ( m ) \varphi(m) φ(m) 的大小,然后按照对应的公式计算即可。
代码
inline int phi(int x) {
int ans = x;
for(register int i = 2;i <= sqrt(x);i ++)
if(x % i == 0) {
ans = ans / i * (i - 1);
while(x % i == 0)
x /= i;
}
if(x > 0)
ans = ans / x * (x - 1);
return ans;
}
inline int ksm(int base , int x , int m) {
int result = 1;
while(x) {
if(x & 1)
result = result * base % m;
x >>= 1;
base = base * base % m;
}
return result;
}
inline int solve(int a , int b , int m) {
int phi_m = phi(m);
if(b >= phi_m)
return ksm(a , b % phi_m + phi_m , m);
else
return ksm(a , b , m);
}
证明不放了,以后再 update 吧。