数论-乘法逆元

版权声明:转载请留言 https://blog.csdn.net/qq_40744093 https://blog.csdn.net/qq_40744093/article/details/86770257

                                            乘法逆元

[用途]

求解关于a/b(mod p)的问题

[介绍]

我们假设x为a的乘法逆元,为什么要求乘法逆元呢?当a/b非常大导致溢出时,程序必然出错。我们可以将a/b(mod p)转化为ax(mod)p;根据乘法逆元的定义,在模p的意义下有:ax≡1 (mod) p

如果乘法逆元x存在,a,p一定互素!

[适用条件]

  • 当p为素数时,可以使用费马小定理求解
  • 当p为合数时,使用欧拉定理求解
  • 不管p是素数还是合数,都可以使用拓展欧几里得求解(推荐)

 

拓展欧几里得

对于ax≡1 (mod) p可以化为ax+bp=1;如果乘法逆元x存在,a,p一定互素,即gcd(a,p)=1;反之a,p不互素,则乘法逆元x不存在;而拓展欧几里得刚好用于求解ax+by=gcd(a,b),我们可以根据gcd(a,b)是否等于1来判断乘法逆元x是否存在。

#include<iostream>
using namespace std;
typedef long long LL;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
	if(!b){
		x=1;
		y=0;
		return a;
	}
	LL gcd=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return gcd;
}
int main()
{
    LL a,b;
    while(cin>>a>>b){
    	LL x,y;
    	if(exgcd(a,b,x,y)!=1)cout<<"a的乘法逆元不存在"<<endl;
		else cout<<"a的乘法逆元为: "<<x<<endl; 
    }
    return 0;
}

费马小定理

当a是正整数,p是素数时,有a^(p-1)≡1 (mod) p,则乘法逆元x=a^(p-2),根据快速幂求解即可

#include<iostream>
using namespace std;
typedef long long LL;
LL ksm(LL x,LL n,LL mod)
{
	x%=mod;
	LL ans=1;
	while(n){
		if(n&1)ans=ans*x%mod;
		x=x*x%mod;
		n>>=1;
	}
	return ans;
}
int main()
{
	LL a,mod;
	while(cin>>a>>mod)cout<<"a的乘法逆元为:"<<ksm(a,mod-2,mod)<<endl; 
    return 0;
}

 

欧拉定理

有a^phi(p)≡1 (mod) p,a的乘法逆元x即为phi(p)

积性函数:函数f(x)对任意互素的两个数x1,x2,有f(x1*x2)=f(x1)*f(x2)

完全积性函数:函数f(x)对任意两个正整数数x1,x2,有f(x1*x2)=f(x1)*f(x2)

对于积性函数有:f(p)=f(p1^k1)*f(p2^k2)*f(p3^k3)....*f(pn^kn),其中p1、p2...pn为不同的素数,k1、k2....kn为非负整数

欧拉函数是积性函数,也有phi(p)=phi(p1^k1)*phi(p2^k2)*phi(p3^k3)....*phi(pn^kn)

对于欧拉函数还有以下结论成立:

结论一:p为素数:phi(p)=p-1;

结论二:p为素数且k是p的倍数:phi(p*k)=phi(k)*p;

结论三:p,q互素:phi(p*q)=phi(p)*phi(q)

结论四:p为素数,k为正整数,phi(p^k)=p^k-p^(k-1)=(p-1)*p^(k-1),

 

欧拉筛法 时间复杂度为O(n)

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long LL;
const LL N=1e6+99;
LL su[N],phi[N],num;
bool vis[N];
void init()
{
    memset(vis,true,sizeof(vis));
    num=0;
    vis[0]=vis[1]=false;
    phi[1]=1;									/*phi[1]特例*/ 
    for(LL i=2;i<=N;++i){						/*欧拉筛法*/ 
        if(vis[i]){
            su[++num]=i;
            phi[i]=i-1;							/*结论一*/
        }
        for(LL j=1;j<=num&&i*su[j]<=N;++j){
            vis[i*su[j]]=false;
            if(i%su[j]==0){						/*结论二*/
                phi[i*su[j]]=phi[i]*su[j];
                break;
            }
            else phi[i*su[j]]=phi[i]*phi[su[j]];/*结论三*/
        }
    }
    /*for(LL i=1;i<=100;++i)cout<<su[i]<<" ";
    cout<<endl;
    for(LL i=1;i<=100;++i)cout<<phi[i]<<" ";*/
}
int main()
{
    init();
    return 0;
}

一般的,p不会超过int范围,求phi(p)时如果选择欧拉筛法打表,时间复杂度为O(n),很有可能是超时的,所以我们可以选择时间复杂度为O(n) (仅仅求单个) 的方法。

另一条重要的定理是算数基本定理:任何一个大于1的自然数 N,可以唯一分解成有限个质数的乘积,即N=(p1^k1)*(p2^k2)*(p3^k3)....(pn^kn),其中p1、p2...pn为不同的素数,k1、k2....kn为非负整数,而对于欧拉函数有

phi(p)=phi(p1^k1)*phi(p2^k2)*phi(p3^k3)....*phi(pn^kn),利用结论四:p为素数,k为正整数,phi(p^k)=p^k-p^(k-1)=(p-1)*p^(k-1);再结合筛法(埃式与欧拉都行)求2~p的素数即可,时间复杂度O(n)

#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const LL N=1e3+99;
LL su[N],num;
bool vis[N];
void init()
{
    memset(vis,true,sizeof(vis));
    num=0;
    vis[0]=vis[1]=false;	
    for(LL i=2;i<=N;++i){	
        if(vis[i])su[++num]=i;
        for(LL j=1;j<=num&&i*su[j]<=N;++j){
            vis[i*su[j]]=false;
            if(i%su[j]==0)break;
        }
    }
    /*for(LL i=1;i<=100;++i)cout<<su[i]<<" ";
    cout<<endl;*/
}
LL get(LL p)
{
	LL ans=1;
	for(int i=1;i<=num;++i){
		if(p%su[i]==0){
			int j=-1;
			while(p%su[i]==0){
				++j;   				/*求指数j即结论四中的k-1*/ 
				p/=su[i];
			}
			while(j--)ans*=su[i];	/*求p^(k-1)*/ 
			ans*=(su[i]-1);			/*求(p-1)*p^(k-1)*/
			if(p==1)break;
		}
	}
	return ans;
}
int main()
{
    init();
    LL p;
    while(cin>>p)cout<<"phi(p):"<<get(p)<<endl; 
    return 0;
}

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_40744093/article/details/86770257
今日推荐