【什么是逆元】
在数学世界里,“逆元”是一个非常重要的概念。
顾名思义,“逆”通常表示“相反”或“逆向操作”,而“逆元”则是就某种特定运算而言与“单位元”相对的一种特殊元素。
换言之,在加法运算下,能够与某元素相加从而得到加法运算的单位元(通常是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