对于逆元其实说难不难,说简单也不简单。
概念:
对于a * x≡b(mod m)这个方程如果我们要求解的话其实是比较复杂的,可是如果我们可以求出a * y≡1(mod m)中的y的话,在上面那个方程上同乘以y就可以得到,x=b * y,是不是很神奇,我们也称y是a在mod m的条件下的逆元,写作x ^ -1。
注意:
如果m是p的倍数,那么m在mod p的意义下是没有逆元的!!!
求法:
对于我们的逆元求法有四种,这四种各有千秋,我们要根据题目来决定采用哪种方法。
拓展欧几里得:
不知道拓展欧几里得的点这
对于求解这个方程: a * y≡1(mod m),我们其实可以这么写a * y+k * m=1,(这个k为任意整数),这个其实就是我们的拓展欧几里得,当然前提是gcd(a,m)=1
代码:
#include
#include
using namespace std;
int x,y;
void exgcd(int a,int b)
{
int k;
if(b==0)//边界
{
x=1;
y=0;
return ;
}
exgcd(b,a%b);
k=x;//因为x和y都会改变,所以我们开个变量储存
x=y;//直接代入我们的公式
y=k-a/b*y;
}
int main()
{
int a,b;
scanf("%d %d",&a,&b);//我们要求a*y≡1(mod b)
exgcd(a,b);//要求a*x+b*y=gcd(a,b)
printf("%d",(x+b)%b);//因为求出来的可能是负数所以我们转化成正数
return 0;
}
快速幂:
这个其实是用费马小定理来求的。
如果p是素数(也叫质数),对于任意的x ^ p≡x(mod p),这个定理就是费马小定理,当且仅当x无法被p整除可以得到,x ^ (p-1)≡1(mod p)(因为如果x可以被p整除的话x mod p一定为0,而不可能为1),然而我们再同除以一个x,可以得到x^-1≡x ^ (p-2)(mod p),所以我们只要求出x的p-2次方mod p就可以了,这就是快速幂。
代码:
#include
#include
using namespace std;
long long pow(long long a,long long b,long long mod)
{
long long ans=1;
while(b)
{
if(b%2==1)//如果b是奇数
ans=(ans*a)%mod;//因为b最后一定会变为1,所以我们只要在这边计算ans就好了
a=(a*a)%mod;//a尝龟乘
b=b/2;
}
return ans;
}
int main()
{
long long x,p,ans;
scanf("%lld %lld",&x,&p);//求x在mod p意义下的方程
ans=pow(x,p-2,p);
printf("%lld",ans);
return 0;
}
线性递推:
这个的时间复杂度是O(n)的,如果单单求一个逆元的话是比上面的都慢的,可如果要求多个的话这个就显现出来的优势。
我们线性递推其实就是找出逆元与逆元之间的关系,通过已知的逆元求出未知的逆元。
推导:
首先我们设p=k * i+r,然后可以知道k * i+r≡0(mod p)(因为k * i+r本身等于p,所以p mod p为0),然后我们两边同乘r^-1 * i ^ -1,就得到k * r ^ -1 +i ^ -1 ≡0(mod p),移项得到i ^ -1≡-k * r ^ -1(mod p),我们可以知道k其实就是p/i下取整,也就是⌊ p/i ⌋,r其实就是p%i,(这个好好想想是重点),所以我们得到公式:i ^ -1≡-⌊ p/i ⌋ * (p%i) ^ -1(mod p)
代码:
#include
#include
using namespace std;
long long inv[3000100];
int main()
{
long long n,p;
inv[1]=1;//边界,因为1的逆元一定是1
scanf("%lld %lld",&n,&p);//求1到n中所有在mod p的情况下的逆元
for(int i=2;i<=n;i++)
inv[i]=(-(p/i)*inv[p%i]%p+p)%p;//根据公式递推,加p的原因是把负数转化成正数
for(int i=1;i<=n;i++)
printf("%lld\n",inv[i]);//输出答案
return 0;
}
线性递推求阶乘逆元:
为什么要有这个,因为组合数也可以用阶乘逆元求,所以我们也介绍一下。
推导:
我们定义inv[i]表示i!的逆元,
我们可以知道inv[i+1]=(1/(i+1)!) ^ -1,(因为csdn比较难渲染数学公式,所以如果不方便看的话可以自己在纸上写),我们同乘i+1就变成了,inv[i+1]*(i+1)=(1/i!) ^ -1=inv[i],所以我们可以得到:inv[i+1] * (i+1)=inv[i],所以我们先求出n!的逆元,再倒推回来。n!的逆元怎么求?用费马小定理或者拓展欧几里得。
代码:
#include
#include
using namespace std;
long long inv[3000100];
long long pow(long long a,long long b,long long mod)
{
long long ans=1;
while(b)
{
if(b%2==1)//如果b是奇数
ans=(ans*a)%mod;//因为b最后一定会变为1,所以我们只要在这边计算ans就好了
a=(a*a)%mod;//a尝龟乘
b=b/2;
}
return ans;
}
int main()
{
long long n,p;//求1到n在mod p意义下所有阶乘的逆元
inv[1]=inv[0]=1;
scanf("%lld %lld",&n,&p);
for(int i=1;i<=n;i++)//我们先求一遍阶乘
inv[i]=(inv[i-1]*i)%p;
inv[n]=pow(inv[n],p-2,p);//用费马小定理求逆元
for(int i=n-1;i>=1;i--)//倒推
inv[i]=(inv[i+1]*(i+1))%p;
for(int i=1;i<=n;i++)
printf("%lld\n",inv[i]);
return 0;
}
上面的是费马小定理的。
#include
#include
using namespace std;
long long inv[3000100],x,y;
void gcd(long long a,long long b)
{
if(b==0)//边界
{
x=1;
y=0;
return ;
}
gcd(b,a%b);
long long k=x;//因为x和y都会改变,所以我们开个变量储存
x=y;
y=k-a/b*y;//直接代入我们的公式
return ;
}
int main()
{
long long n,p;//求1到n在mod p意义下所有阶乘的逆元
inv[1]=inv[0]=1;
scanf("%lld %lld",&n,&p);
for(int i=1;i<=n;i++)//我们先求一遍阶乘
inv[i]=(inv[i-1]*i)%p;
gcd(inv[n],p);
inv[n]=(x+p)%p;//用费拓展欧几里得求逆元
for(int i=n-1;i>=1;i--)//倒推
inv[i]=(inv[i+1]*(i+1))%p;
for(int i=1;i<=n;i++)
printf("%lld\n",inv[i]);
return 0;
}
上面是拓展欧几里得的。
以上就是逆元的所有知识了,希望大家看后可以有个理解。如果有不懂得欢迎留言。