概念引入
学习逆元之前,首先要知道什么叫逆元
- 逆元这个概念通常是用来解决除法求模问题的,关于求模,有下面的公式
( a + b ) % c = ( a % c + b % c ) % c ( a − b ) % c = ( a % c − b % c ) % c ( a ∗ b ) % c = ( a % c ∗ b % c ) % c (a+b)\%c=(a\%c+b\%c)\%c\\(a-b)\%c=(a\%c-b\%c)\%c\\(a*b)\%c=(a\%c*b\%c)\%c (a+b)%c=(a%c+b%c)%c(a−b)%c=(a%c−b%c)%c(a∗b)%c=(a%c∗b%c)%c - 可以看出,对于除法没有相应的取模公式,这是因为除法的类似公式是不成立的,可以举例说明。有时候a和b很大,计算机存不下,不能计算之后再取模,所以我们想找到一些途径在中间过程中取模,这样,就想能不能变除法为乘法呢?
- 设
b
×
k
%
c
=
1
b\times k\%c=1
b×k%c=1,则有
( a b ) % c = ( a b % c ∗ ( b k ) % c ) = ( a ∗ k ) % c (\frac{a}{b})\%c=(\frac{a}{b}\%c*(bk)\%c)=(a*k)\%c (ba)%c=(ba%c∗(bk)%c)=(a∗k)%c
这样就把除法转化为了乘法,这里面的k就叫做b关于c的乘法逆元,写成数学表达式就是
b k ≡ 1 ( m o d c ) bk\equiv1\pmod c bk≡1(modc)
读作 b k bk bk和 1 1 1对于模 c c c同余 - 初学时很容易就能联想到我们曾经学过的倒数这一概念,也就是一个数的倒数乘以自身等于1,在这里逆元是数论倒数(算术倒数)并非普通意义上的倒数,是取模引入的概念,不要弄混
求解方法
费马小定理
这个定理的内容是这样的:
如果p为质数,a不是p的倍数,那么有
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1}\equiv1\pmod p
ap−1≡1(modp)
- 关于这个定理,不作证明,可以自行百度。研究问题有时候也不要太面面俱到,关系不大。以有涯逐无涯,殆矣
- 观察此定理和上面的式子,可以发现这个式子可以变化为
a × a p − 2 ≡ 1 ( m o d p ) a\times a^{p-2}\equiv1\pmod p a×ap−2≡1(modp)
正好和上面式子里的b和k对应,也就是说,a的乘法逆元为ap-2,当然,需要满足费马小定理的条件
模板题
尝试一下
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
int Data[MAXN];
ll GCD(ll a, ll b){
return b == 0 ? a : GCD(b, a % b);
}
ll fastpow(ll base, ll power, ll MOD){
ll ans= 1;
while(power){
if(power & 1) ans = ans * base % MOD;
base = base * base % MOD;
power >>= 1;
}
return ans % MOD;
}
int main(){
int t;
ll y, p;
cin >> t;
while(t--){
cin >> y >> p;
if(GCD(y, p) != 1) cout << -1 << "\n";
else cout << fastpow(y, p - 2, p) << "\n";
}
return 0;
}
- 使用时候注意需要满足条件
扩展欧几里德
- 还是这样一个式子
b k ≡ 1 ( m o d c ) bk\equiv1\pmod c bk≡1(modc)
- 回顾一下,b关于c的逆元为k,现在我们要求k,这里面b、c都已知
- 很容易能够知道GCD(b, c)或者说b和c的最大公约数应该为1,或者说b,c互质,如果不互质那么模就不会是1,所以这个方程等价于求下面这个方程的解
b x + c y = 1 bx+cy=1 bx+cy=1 - 求出的x即为逆元(还需要考虑负数的情况),至于方程的解法,因为b和c互质,右边是1,所以是一个明显的扩展欧几里德(关于扩展欧几里德请看这篇),如果让判断是否无解,那么b和c如果不互质,那么无解
- 如果解出来x是负数,那么需要通过c来调整x为正数,具体如下
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
int Data[MAXN];
void extend_gcd(ll a, ll b, ll &d, ll &x, ll &y){
if(b == 0){
x = 1;
y = 0;
d = a;
return;
}
extend_gcd(b, a % b, d, x, y);
ll tmp = x;
x = y;
y = tmp - a / b * y;
}
ll mod_reverse(ll y, ll p){
ll x, d;
extend_gcd(y, p, d, x, y);
return d == 1 ? (p + x % p) % p : -1;
}
int main(){
int t;
ll y, p;
cin >> t;
while(t--){
cin >> y >> p;
cout << mod_reverse(y, p) << "\n";
}
return 0;
}
模板题
- 可以用上面的两种算法尝试一下这道题,扩展欧几里得更快一些,但是最后一个点都是TLE,由于这道题比较特殊,它要求求解前n个数的逆元,而n范围又比较大,上面两种算法适用于的情况是单一计算逆元,或者计算数量较少的逆元,如果要求很多个数的逆元,需要使用更好的方法
另一重要结论
- 如果分母是固定的且比较小,模数与分母不互质,那么有下面的公式成立
a b % m = a % ( b × m ) b \frac{a}{b}\%m=\frac{a\%(b\times m)}{b} ba%m=ba%(b×m) - 证明,令 x = a b m , y = a b % m x=\frac{a}{bm},y=\frac{a}{b}\%m x=bma,y=ba%m,有 a b = m x + y , a = b m x + b y , a % ( b m ) = b y \frac{a}{b}=mx+y,a=bmx+by,a\%(bm)=by ba=mx+y,a=bmx+by,a%(bm)=by
- 所以 y = a % ( b × m ) b , a b % m = a % ( b × m ) b y=\frac{a\%(b\times m)}{b},\frac{a}{b}\%m=\frac{a\%(b\times m)}{b} y=ba%(b×m),ba%m=ba%(b×m)
- 这个结论有时能用上,注意如果使用快速幂计算 a a a,那么这个时候需要对 b m bm bm取模