数论之逆元

在数学里,我们知道+、-、*是满足对%的分配律的,但是除法是不满足的,然而对于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则A^{\varphi(P)}=1(mod\ P)。故我们可以知道,A^{\varphi(P)-1}*A=1(mod\ P)  ,故A^{\varphi(P)-1}即为A在模P意义下的逆元。

现在的问题转换为如何求解欧拉函数:

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)个数为 d_{1},d_{2},...,d_{\varphi(x) },由于 a | x,所以 a 与 这φ(x)个数一定互质。那么 < ax且与 ax 互质的数则有 \tiny d_{1}+0*x,...,d_{\varphi(x)}+0*x,d_{1}+1*x,...,d_{\varphi(x)}+1*x,...,d_{1}+(a-1)*x,...,d_{\varphi(x)}+(a-1)*xa组,每一组有φ(x)个,故一共有 a*φ(x)个,即 φ(a*x) = a * φ(x)。

4. 若 a , b 互质,则有 φ(a) * φ(b) = φ(a * b)  欧拉函数是积性函数。

我们可以利用性质1、2、3求解欧拉函数,如果我们做质因数分解(数论中可以证明,任何一个数都可以被分解为若干个质因数的幂次的乘积),即

                                                                                 \tiny x = p_{1}^{k_{1}} * p_{2}^{k_{2}} *...* p_{n}^{k_{n}}

利用性质4我们得到:

                                                                    \tiny \varphi(x) = \varphi(p_{1}^{k_{1}})*\varphi(p_{2}^{k_{2}})*...*\varphi(p_{n}^{k_{n}}) 

由于质数之间一定是两两互质的,因此利用性质2得到:

                                                    \tiny \varphi(x) = p_{1}^{k_{1}-1}\varphi(p_{1})*p_{2}^{k_{2}-1}\varphi(p_{2})*...*p_{n}^{k_{n}-1}\varphi(p_{n})

由于:

                                                                          \tiny x = p_{1}^{k_{1}} * p_{2}^{k_{2}} *...* p_{n}^{k_{n}}

所以我们得到:

                                                         \tiny \varphi(x) = x*\frac{\varphi(p_{1})}{p1}*\frac{\varphi(p_{2})}{p2}*...*\frac{\varphi(p_{n})}{pn}

再次根据性质2得:

                                                         \tiny \varphi(x) = x*\frac{p_{1}-1}{p1}*\frac{p_{2}-1}{p2}*...*\frac{p_{n}-1}{pn}

方式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.线性求解逆元

                                                                           p=k*i+r(0<r<i)

                                                                            k*i+r=0(mod+p)

两边同乘逆元i^{-1}r^{-1}可得:

                                                                        k*r^{-1}+i^{-1}=0(mod+p)

                                                                          i^{-1}=-k*r^{-1}(mod+p)

                                                                               i^{-1}=(-p/i)*r^{-1}

边界条件为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),但是如果频繁的计算还是推荐线性递推或者采用记忆化递归的方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值