逆元的两种常见计算方法

概念引入

学习逆元之前,首先要知道什么叫逆元

  • 逆元这个概念通常是用来解决除法求模问题的,关于求模,有下面的公式
    ( a + b ) % c = a % c + b % c ( a − b ) % c = a % c − b % c ( a ∗ b ) % c = ( a % c ∗ b % c ) % c (a+b)\%c=a\%c+b\%c\\(a-b)\%c=a\%c-b\%c\\(a*b)\%c=(a\%c*b\%c)\%c (a+b)%c=a%c+b%c(ab)%c=a%cb%c(ab)%c=(a%cb%c)%c
  • 可以看出,对于除法没有相应的取模公式,这是因为除法的类似公式是不成立的,可以举例说明。有时候a和b很大,计算机存不下,不能计算之后再取模,所以我们想找到一些途径在中间过程中取模,这样,就想能不能变除法为乘法呢?
  • 设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)=(ak)%c
    这样就把除法转化为了乘法,这里面的k就叫做b关于c的乘法逆元,写成数学表达式就是
    b k ≡ 1 ( m o d c ) bk\equiv1\pmod c bk1(modc)
    读作bx和1对于模c同余
  • 初学时很容易就能联想到我们曾经学过的倒数这一概念,也就是一个数的倒数乘以自身等于1,在这里逆元是数论倒数(算术倒数)并非普通意义上的倒数,是取模引入的概念,不要弄混

求解方法

费马小定理

这个定理的内容是这样的:
如果p为质数,a不是p的倍数,那么有
a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv1\pmod p ap11(modp)

  • 关于这个定理,不作证明,可以自行百度。研究问题有时候也不要太面面俱到,关系不大。以有涯逐无涯,殆矣
  • 观察此定理和上面的式子,可以发现这个式子可以变化为
    a × a p − 2 ≡ 1 ( m o d p ) a\times a^{p-2}\equiv1\pmod p a×ap21(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 bk1(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范围又比较大,上面两种算法适用于的情况是单一计算逆元,或者计算数量较少的逆元,如果要求很多个数的逆元,需要使用更好的方法

猜你喜欢

转载自blog.csdn.net/roadtohacker/article/details/114582289