“乘法逆元”的常见求解方法 ← exgcd+费马小定理+逆元预处理

【什么是逆元】
在数学世界里,“逆元”是一个非常重要的概念。
顾名思义,“逆”通常表示“相反”或“逆向操作”,而逆元”则是就某种特定运算而言与“
单位元”相对的一种特殊元素
换言之,
在加法运算下,能够与某元素相加从而得到加法运算的单位元(通常是0)的那一特殊元素,就被我们称为“加法逆元”。与此相对应,在乘法运算下,能够与某元素相乘从而得到乘法运算的单位元(通常是1)的那一特殊元素,就被我们称为“乘法逆元”。

【逆元的常见求解方法】

在算法竟赛中,通常有三大类方法求逆元。
(一)
扩展欧几里得算法(详见:https://blog.csdn.net/hnjzsyjyj/article/details/147673627
扩展欧几里得算法(Extended Euclidean Algorithm)是欧几里得算法的扩展版本,用于求解 ax+by=gcd(a,b) 的整数解 x 和 y。
当 gcd(a,b)=1 时,方程 ax+by=1 的解 x,即为 a 在模 b 下的逆元
注意:利用扩展欧几里得算法求逆元时,
模数p是不是质数均适用

#include <bits/stdc++.h>
using namespace std;
 
int exgcd(int a,int b,int &x,int &y) {
    if(b==0) {
        x=1,y=0;
        return a;
    }
    int d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
 
int main() {
    int a,b,x,y;
    cin>>a>>b;
    int gcd=exgcd(a,b,x,y);
    cout<<"x="<<x<<"\t"<<"y="<<y<<endl;
    cout<<"gcd="<<gcd<<endl;
 
    return 0;
}
 
/*
in:
6 9

out:
x=-1    y=1
gcd=3
*/

(二)费马小定理若 a 是质数,p 是与 a 互质的正整数,则有 a^(p-1)≡1(mod p)。
费马小定理求逆元,仅当
模数p为质数且a与p互质时适用。若p非质数,需改用扩展欧几里得算法求解逆元。
费马小定理为质数模数下的逆元求解提供了高效方法,其核心是通过幂运算
a^(p-2) mod p直接得到逆元,广泛应用于密码学(如RSA算法)和组合数学。

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

bool isPrime(int n) {
    if(n<2) return false;
    for(int i=2; i<=sqrt(n); i++) {
        if(n%i==0) return false;
    }
    return true;
}

LL gcd(LL a,LL b) {
    if(b==0) return a;
    else return gcd(b,a%b);
}

LL fastPow(LL a,LL n,LL p) {
    LL ans=1;
    while(n) {
        if(n & 1) ans=ans*a%p;
        n>>=1;
        a=a*a%p;
    }
    return ans%p;
}

int main() {
    LL a,p;
    cin>>a>>p;
    if(isPrime(p) && gcd(a,p)==1) {
        cout<<fastPow(a,p-2,p);
    }
    return 0;
}

/*
in:7 13
out:2
*/

(三)逆元预处理
若在竟赛中需
频繁计算 1, 2, …, n 的逆元,也可以用“逆元预处理“技巧。其可以仅需 O(n) 时间得到所有值的逆元。
● 预处理 1~n 模 p 的逆元‌的理论证明

定理:inv[i]=(p-p/i)*inv[p%i]%p,其中 p 为质数。
证明:设 k=p/i,r=p%i,则有 p=k*i+r
两边模 p 得:k*i+r≡0 (mod p) → i≡-r/k (mod p)
因此,inv[i]≡-k*inv[r] (mod p)。
之后,利用公式 (p+x%p)%p 将 inv[i]≡-k*inv[r] (mod p) 调整为对应的正数:
inv[i]=(p-k*inv[r]%p)%p=(p-(p/i)*inv[p%i]%p)%p。
由于 p≡0 (mod p),故 (p-(p/i)*inv[p%i]%p)%p 可简化为:−(p/i)*inv[p%i]%p
又根据模运算性质 -x≡p-x(mod p),
所以,−(p/i)*inv[p%i]%p 等价于 (p−(p/i)*inv[p%i])%p=(p−p/i)*inv[p%i]%p
综上,得证。

● 预处理 1~n 模 p 的逆元‌的模板代码
(1)满分代码一:
inv[i]=(p-p/i)*inv[p%i]%p

#include <bits/stdc++.h>
using namespace std;
 
typedef long long LL;
const int N=3e6+5;
LL inv[N];
 
int main() {
    LL n,p;
    scanf("%lld %lld",&n,&p);
 
    inv[1]=1;
    for(int i=2; i<=n; i++) {
        inv[i]=(p-p/i)*inv[p%i]%p;
    }
 
    for(int i=1; i<=n; i++) {
        printf("%lld\n",inv[i]);
    }
 
    return 0;
}
 
/*
in:
10 13

out:
1
7
9
10
8
11
2
5
3
4
*/

(2)满分代码二:inv[i]=(p-(p/i)*inv[p%i]%p)%p

#include <bits/stdc++.h>
using namespace std;
 
typedef long long LL;
const int N=3e6+5;
LL inv[N];
 
int main() {
    LL n,p;
    scanf("%lld %lld",&n,&p);
 
    inv[1]=1;
    for(int i=2; i<=n; i++) {
        inv[i]=(p-(p/i)*inv[p%i]%p)%p;
    }
 
    for(int i=1; i<=n; i++) {
        printf("%lld\n",inv[i]);
    }
 
    return 0;
}
 
/*
in:
10 13

out:
1
7
9
10
8
11
2
5
3
4
*/





【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/136276606
https://blog.csdn.net/hnjzsyjyj/article/details/127699346
https://blog.csdn.net/hnjzsyjyj/article/details/147778571
https://mp.weixin.qq.com/s/ue2wQsbKguCnfGINZXYW5g

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值