在数学里,我们知道+、-、*是满足对%的分配律的,但是除法是不满足的,然而对于a / b % c该怎么办呢。
这种时候就要用到取模意义下的逆元。
首先介绍一下一般的逆元、幺元。幺元就是在对应的运算中,和自己进行这种运算的结果不变的元素,加减法中幺元为0,乘法中为1。而x的逆元和x的运算结果要等于幺元,所以,加减法中x的逆元为-x,而乘法中为1/x。
对于除法,我们将除法变成乘法后,就可以用取模的分配律,这里的逆元实际上是乘法的逆元。
乘法的逆元我们表示为1/x。在单纯的乘法中,它的确就是1/x。但是在取模的意义下,我们要求所有的数都是整数,显然1/x是不满足的,但是我们可以用1/x来表示某一个整数p,p*x=1(mod m)。这样,在面对a / b( mod m )的时候,我们通过1 / b的逆元将除法变成乘法,就可以求解了。
求解逆元的几种方法:
1.扩展欧几里得算法
设A,B互质,则根据贝祖公式有:x * A +y * P = 1。
扩展欧几里得可以求出 x,y ,x * A + y * P = 1,两边同时对P取模则可以化简为 x * A = 1 ( mod P ),x为 A 在模为 P 时的逆元。
#include<iostream>
using namespace std;
int exgcd(int A,int B,int& X1,int& Y1){
if(B==0){
X1=1;Y1=0;
return A;
}
int d=exgcd(B,A%B,X1,Y1);
int X0=Y1;
int Y0=X1-A/B*Y1;
X1=X0;
Y1=Y0;
return d;
}
//本质上就是求解 AX=1(mod P)也就是 AX-PY=1,然而Y本身就是变量,所以无所谓正负,可以表示为 AX+PY=1 解出一个最小正整数的X即可,因为为负数的话,可能导致歧义(负数的模在计算机语言和数学中定义不一样)
int main(){
int A,P,X,Y;
cin>>A>>P;
if(exgcd(A,P,X,Y)!=1){
cout<<"不存在逆元";
}else{
cout<<A<<"*"<<(X%(P/1)+(P/1))%(P/1)<<"=1(mod "<<P<<")";
}
return 0;
}
2.费马小定理(费马小定理是欧拉定理当模数为素数的时候的特例)
如果P为素数并且gcd( A , P )=1,那么有定理A^(P-1) = 1 (mod P),可得A*A^(P-2)=1(mod P),所以A在mod P情况下的逆元就是A^(P-2),快速幂求解即可。
#include<iostream>
using namespace std;
int gcd(int A,int B){
while(B^=A^=B^=A%=B);
return A;
}
int quickPow(int A,int X,int P){
int res=1;
while(X){
if(X&1){
res=res*A%P;
}
X>>=1;
A=A*A%P;
}
return res;
}
bool isPrime(int X){
for(int i=2;i*i<=X;++i){
if(X%i==0)return false;
}
return true;
}
int main(){
int A,P;
cin>>A>>P;
if(!isPrime(P))cout<<"P不是质数,无法使用费马定理"<<endl;
if(gcd(A,P)!=1)cout<<"不互质,无法使用费马定理"<<endl;
else cout<<A<<"*"<<quickPow(A,P-2,P)<<"=1(mod "<<P<<")";
return 0;
}
3.欧拉定理
先介绍欧拉函数φ(x)=n,n为小于x的正整数中与x互质的元素个数(包括1)。根据欧拉定理,当gcd(A,P)=1则。故我们可以知道, ,故即为在模意义下的逆元。
现在的问题转换为如何求解欧拉函数:
1. 首先欧拉函数约定 φ(1) = 1
2. 若P为质数,则φ(P^n) = P^(n-1) * (P-1) ,φ(P) = P-1
证明:由于P是质数,因此在1~P^n中,与P不互质的数 x 一定满足 x = y * P ( y <= P^(n-1) ),这样的数一共只有 P^(n-1)个,因此φ(P^n) = P^n - P^(n-1) = P^(n-1) * (P - 1)。
3. 若 a | x,则有φ(a * x) = a* φ(x)
证明:设 <x 且与 x互质的φ(x)个数为 ,由于 a | x,所以 a 与 这φ(x)个数一定互质。那么 < ax且与 ax 互质的数则有 a组,每一组有φ(x)个,故一共有 a*φ(x)个,即 φ(a*x) = a * φ(x)。
4. 若 a , b 互质,则有 φ(a) * φ(b) = φ(a * b) 欧拉函数是积性函数。
我们可以利用性质1、2、3求解欧拉函数,如果我们做质因数分解(数论中可以证明,任何一个数都可以被分解为若干个质因数的幂次的乘积),即
利用性质4我们得到:
由于质数之间一定是两两互质的,因此利用性质2得到:
由于:
所以我们得到:
再次根据性质2得:
方式1:枚举质因数
int phi(int n){
int res = n;
for( int i=2; i*i<=n; ++i){
if( n % i == 0 ){
res = res / i * (i-1); //先除后乘 防止溢出
}
while( n % i == 0){ //除干净因子
n / = i;
}
} // 经过这个循环之后,n 只可能是1 或者是最后没有被分解到的那个素数的幂次
if( n > 1 ){
res = res / n * ( n - 1 );
}
return res;
}
除了1以外。没有其他数的欧拉值等于自身,故欧拉值等于自身可以当作当前这个数没有被筛过的充要条件 ,于是我们可以在素数筛法中求欧拉值。
方式2:在埃氏筛法中同时求得欧拉函数
void EsPrime(int n){
for(int i=1; i<=n; ++i) phi[i] = i;//除了1以外,没有其他数的欧拉值为1 所以欧拉值为1可以当作是没有被筛过的充要条件
for(int i=2; i<=n; ++i){
if(phi[i] == 1){//没被筛到过 是素数
for(int j = i; j <= n; j += i){//给所有拥有该素数作为因子的数更新欧拉值
phi[j] = phi[j] / i * (i - 1);//素数p 在这一步 phi[p] = p-1
}
}
}
}
方式3:在欧拉筛中求解欧拉值
int Primes[10010],cnt;
void EulerPrime(int n){
phi[1] = 1;
for( int i = 1; i <= n ; ++ i){
phi[i] = i;
}
for(int i=2; i<=n; ++i){
if( phi[i] == i ){// i 是 素数
phi[i] = i-1;
Primes[cnt++] = i;
}
for(int j=0; j < cnt && Primes[j]*i <= n; ++j){
if( i % Primes[j] == 0){
phi[Primes[j] * i] = Primes[j] * phi[i]; //用性质3
break;
} else{ // Primes[j]是质数 i % Primes[j] != 0 因此两个数必定互质
phi[Primes[j] * i] = phi[Primes[j]] * phi[i];
}
}
}
}
4.线性求解逆元
;
;
两边同乘逆元可得:
;
;
;
边界条件为r = 1。
线性递推方式:
int inv[100005],mod=1e9+7;
inv[1]=1;
for(int i=2;i<=m;i++){
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
}
理论上直接inv[i]=(-mod/i)*inv[mod%i]%mod)即可,但是为了避免求出负数逆元,使用inv[i]=(mod-mod/i)*inv[mod%i]%mod,这样并不会影响最终的逆元结果,但可以获得最小正数逆元(mod*x%mod==0)。
函数递归方式:
int inv(int i) {
if(i==1)return 1;
return (mod-mod/i)*inv(mod%i)%mod;
}
函数递归由于不需要一个一个计算,而取模每次都会至少将规模减少一半,所以计算一次的复杂度为O(log mod),但是如果频繁的计算还是推荐线性递推或者采用记忆化递归的方式。