费马小定理
假如p是质数,且gcd(a,p)=1(a和p互质),那么 a^(p-1) ≡ 1(mod p),即 ( a^(p-1) )%p = 1。
- 可以用这个定理快速求得一个大数的余数。例如:
\(欲求:2^{100}\ \%\ 13=\ ?\)
\(因为2与13互质,故根据费马小定理有:2^{13-1}\equiv1(mod\ 13)\)
\(即:2^{12}\ \%\ 13 = 1\)
\(又:2^{100}=(2^{12})^{8}*2^4\)
\(所以,2^{100}\ \%\ 13=(2^{12})^{8}*2^4\ \%\ 13=((2^{12})^{8}\ \%\ 13)*(2^4\ \%\ 13)\ \%\ 13=((2^{12}\ \%\ 13)^8\ \%\ 13)*(3)\ \%\ 13=3\)
- 求逆元(通过快速幂使得复杂度在\(O(log)\)):
inv(LL a, LL p) { //a关于p的逆元
return qpow(a, p-2, p);
}
关于费马小定理的拓展就是欧拉定理了,其中前置技能是欧拉函数(线性筛求解),可以解决各种同模问题。
关于逆元的求法还有公式变形法(递归、递推)以及拓展欧几里得算法(证明是将二项的不定方程分别mod A以及mod B化简)
关于求逆元公式法的证明:
设x = p % a,y = p / a
则 x + y * a = p
(x + y * a) % p = 0
移项 x % p = (-y) * a % p
x * inv(a) % p = (-y) % p
inv(a) = (p - y) * inv(x) % p
所以 inv(a) = (p - p / a) * inv(p % a) % p
部分代码实现:
#include <iostream>
using namespace std;
typedef long long LL;
LL exgcd(LL A, LL B, LL &x, LL &y) {
if (B == 0) {
x = 1;
y = 0;
return A;
}
LL gcdnum = exgcd(B, A % B, x, y);
LL tmp = y;
y = x - A/B*y; //算上来的x,y计算出新的x,y
x = tmp;
return gcdnum;
}
LL qmul(LL a, LL b, LL mod) {
LL ans = 0;
a %= mod;
while (b) {
if (b&1) {
ans = (ans + a) % mod;
}
a = (a << 1) % mod;
b >>= 1;
}
return ans;
}
LL qpow(LL a, LL b, LL mod) {
LL ans = 1;
a %= mod;
while (b) {
if (b & 1) {
ans = qmul(ans, a, mod);
}
a = qmul(a, a, mod);
b >>= 1;
}
return ans;
}
//法一:费马小定理
LL inv_1(LL a, LL p) { //a关于p的逆元
return qpow(a, p-2, p);
}
//法二:拓展欧几里得
LL inv_2(LL a, LL p) {
LL x, y;
if (exgcd(a, p, x, y) != 1) return -1; //不满足a p 互质
return (x % p + p) % p; //变为正的
}
//法三
LL inv_3(LL a, LL p) {
//求a关于p的逆元,注意:a要小于p,最好传参前先把a%p一下
return a == 1 ? 1 : (p - p / a) * inv_3(p % a, p) % p;
}
const int maxn = (int)2e5 + 5;
const int MOD = (int)1e9 + 7;
int inv[maxn];
void inv_4(){ //法三递推版 O(n)
inv[1] = 1;
for(int i = 2; i < maxn; i ++){
inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;
}
}
int main() {
cout << "inv_1 : " << inv_1(100, MOD) << endl;
cout << "inv_2 : " << inv_2(100, MOD) << endl;
cout << "inv_3 : " << inv_3(100, MOD) << endl;
inv_4();
cout << "inv_4 : " << inv[100] << endl;
return 0;
}