一,定义
1 逆元:
在群G中,∀a∈G,∃a′∈G,s.t.aa′=e,其中e为G的单位元。
2 乘法逆元:
p为素数,记a⋅b=a×bmodp在群(N,⋅)(N,·)中,∀a∈N,∃a′∈N,s.t.aa′=e=1∀a∈N,∃a′∈N,s.t.aa′=e=1。则称a′是a关于modp的逆元。
为了方便表示,且下面的内容都只涉及到相同的p,我们记a关于modp的逆元为inv[a]。
二, 作用
情况1:
在算法竞赛中,经常会遇到求解数据很大,则输出模 [公式] 的解这类要求。加法、减法、乘法等操作,基于同余理论直接取模即可。但遇到除法时,某步中间结果不一定能完成整除,就无法求解了。
对于(36/3%7)的情况。
第一步,36=18,18%7=4。
第二步,4/3无法整除,但是我们可以求出除数3关于模数7的逆元5,35%7=1符合逆元的定义。
所以我们用5代替/3,即4*5=20,而20%7=6,得到正确答案。
*** 对于一个大数m,在计算m/a%b时,对于a的逆元d可以有m*d%b=m/a%b。对于需要在计算中取模并且有除法的题目中非常好用。 ***
情况2:
当我们要求ab的值时,知道答案的范围ans≤k,但是a和b太大无法直接计算。这时候我们就要假设出一个质数L>k,然后求abmodL即可。
三, 求法
1.枚举法
2.快速幂(费马小定理)
欧拉定理,a^ϕ(p)=1(modp)
∴ap−1≡1(modp)
∴a^p−1≡1(modp)
∴a×a(p−2)≡1(modp)∴a×a^(p−2)≡1(modp)
根据逆元的存在唯一性,inv[a]≡a^p−2(modp)
** 适用:如果n nn是一个质数,而整数a aa不是n nn的倍数**
#include <cstdio>
int a,p;
int Pow(int i,int j) {
if (!j) return 1;
int now=Pow(i,j>>1);
now=now*now%p;
if (j&1) now=now*i%p;
return now;
}
int main(void) {
scanf("%d%d",&a,&p);
printf("%d\n",Pow(a,p-2));
return 0;
}
3.扩展欧几里得算法
aa′≡1(modp) ,我们只用找到ax+py=1(ax=-py+1),而a′≡x。
对于ax+py=1中x,y的找法就是扩展欧几里得算法。
我们对a,p最大公约数,顺便维护x和y的值。
** 适用:存在逆元即可求,适用于个数不多但模数b很大的时候,最常用、安全的求逆元方式。**
#include <cstdio>
int a,p;
int x,y;
void exgcd(int a,int b)
{
if (!b) {x=1,y=0;return;}
exgcd(b,a%b);
int _x=x,_y=y;
x=_y,y=_y*(a/b)-_x;
x%=p,y%=p;
}
int main(void)
{
scanf("%d%d",&a,&p);
exgcd(a,p);
printf("%d\n",(x+p)%p);
return 0;
}
4.线性递推求逆元
** 公式: i n v [ i ] = ( p − p / i ) × i n v [ p % i ] % p , i n v [ 1 ] = 1 **
** 用途:可以 O ( n ) 预处理出 [1,n] 所有数的关于模 p 的乘法逆元 **
**适用范围:mod数是不大的素数而且多次调用,比如卢卡斯定理。 **
long long inv[N];
int main(){
int n,p;
cin>>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("%d\n",inv[i]);
return 0;
}
4,递归求逆元
用途: mod数是素数,所以并不好用,比如中国剩余定理中就不好使,因为很多时候可能会忘记考虑mod数是不是素数。
LL inv(LL i)
{
if(i==1)return 1;
return (mod-mod/i)*inv(mod%i)%mod;
}
5,线性求逆元
当用逆元的次数比较多时,一直l o g loglog求可能会T TT,这时线性求出来逆元是个不错的选择
void init()
{
inv[1] = 1;
for (int i = 2; i <= N; i ++)
inv[i] = mul(dec(mod, mod / i), inv[mod % i]);
}