在上个星期做了武大的网络赛以后,看到了逆元的这个知识点,于是决定整理一下,dalao们的文章已经很多了,现在给自己看一下也当作复习吧:呜呜呜咕咕咕,好了正题开始:
1.首先是我最先接触的用费马小定理求解的,这个在我的上一篇博客
(WHU 校赛2019 D题Dandelion的补题以及相关的知识点整理)里面就有关于这个的解法,但是为了自己好复习,就在这里把代码的板子简单的写一下吧:
const long long mod=1e9+7;
long long poww(long long a,long long b,long long mod)
{
long long res=1,ans=a;
while(b!=0)
{
if(b&1)
res=(res*ans)%mod;
ans=(ans*ans)%mod;
b>>=1;
}
return res;
}
long long inverse(long long a,long long mod)
{
return poww(a,mod-2,mod);
}
费马小定理得复杂度是(O(nlogn)),当n到了1e7数据就要爆炸了,这个时候就要用线性求逆元的方法
2.然后时扩展欧几里得解法,然后这个解法之前我们其实都知道的有以前的欧几里得解法来求公约数,然后这个其实扩展了一下,
首先,我们有扩展欧几里得定理知道,定理:对于任意的两个正整数(负整数将负号提至系数)a,b必然存在两个整数x,y使得ax+by=gcd(a,b),
我们记exgcd(a,b,x,y)为方程ax+by=gcd(a,b)的函数,其中x,y均为引用类型,
首先考虑的时边界情况,当gcd(a,b)中b=0的时候,原方程x=1,y=0;
因为gcd(b,a%b)=gcd(a,b); 所以ax+by=gcd(b,a%b)=gcd(a,b)
我们令ta=b,tb=a%b;所以ta*x+tb*y=gcd(ta,tb)=gcd(b,a mod b) 和 ax+by=gcd(a,b);
设方程的一组解为x1,y1,所以有ta*x1+tb*y1=a*y1+b*(x1-(a/b)*y1)=a*x+b*y
所以 x=y1,y=x1-(a/b)*y1;
所以,我们求x1和y1也是这样的同样方法来进行计算,而且我们也有递归的边界条件,所以我们这个是递归,
最终求的方程的一组解
对于a*x+b*y=c 的方程,只要gcd(a,b)%c==0,我们可以先求出ax+by=gcd(a,b)的一组解,然后将x和y分别乘上c/gcd(a,b)即可,
下面 给出板子:
#include<bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
int gcd=exgcd(b,a%b,x,y);
int tx=x,ty=y;
x=ty;
y=tx-(a/b)*ty;
return gcd;
}
int main()
{
int x,y,a,b;
while(~scanf("%d%d",&a,&b))
{
cout<<"a和b的最大公约数为: "<<exgcd(a,b,x,y)<<endl;
cout<<"ax+by=gcd(a,b) 的一组解是: " <<x<<" "<<y<<endl;
}
return 0;
}
可能有人想说,这个和逆元有什么关系呢,举个例子:求关于 x 的同余方程 ax ≡ 1 (mod b)的最小正整数解
这个就是来求相应的逆元,题目一般会保证b为素数,然后我们就可以得出gcd(a,b)=1,所以原式可以转化为ax-kb=1;
我们设y=-k;那么原式就变成了ax+by=1;那么也就是扩展欧几里得算法,我们递归x的值求出来就好了
为了防止最后的结果可能会小于0,我们一般都可以进行如下的操作,
int ans(int a,int n)//ax=1(mod n)
{
int d,x,y;
d=exgcd(a,n,x,y);
if(d==1)
return (x%n+n)%n;
else
return -1;
}
或者直接(x%n+n)%n;就好了;
扩展欧几里得算法来求逆元是相对数据比较大的时候来求的,而且复杂度什么的要比费马小定理来求乘法逆元要低的多;
3.逆元线性筛法
我们刚刚求得逆元都只是一个数字对于一个mod p得逆元,如果我们要求来求一连串数字对于一个mod p得逆元,那么我们应该使用这种方法,
我们令 t=M/i k=M%i,有
t * i + k ≡ 0(modM) 即-t * i ≡ k(mod M)
两边同时除以i * k 有 -t * inv【k】≡ inv【i】(mod M)
再替换t,k,最后有递推式
inv【i】= (M - M / i)* inv【M%i】%M;
初始化inv【1】=1;
然后递推即可,模板如下:
#include<bits/stdc++.h>
using namespace std;
long long inv[100010];
int a,b;
void xian_inv(int n,int p)
{
inv[1]=1;
for(int i=2;i<=n;i++)
{
inv[i]=(long long)(p-p/i)*inv[p%i]%p;
}
}
int main()
{
while(~scanf("%d%d",&a,&b))
{
xian_inv(a,b);
for(int i=1;i<=a;i++)
cout<<inv[i]<<endl;
}
return 0;
}
最后关于求一连串数字有个结论就是:
从1到p modp的所有逆元值对应1到p中的所有值
写到这里我们差不多把最常见的几种方法介绍完了,但是我们介绍的方法其实都有一个共同点,那就是mod p中的p其实都是一个素数,如果不是素数怎么办呢,我们这个时候就要用到欧拉表求逆元(mod不需要为素数),但是这个地方我不知怎么写,我找了资料还没有找到...
这个地方就先留在这个里,以后反过来看的时候再来探究一下,其实这个mod是否为素数的情况当它不是素数的时候我记得好像在中国剩余定理里面好像不是很好使用,菜的咕咕咕:
然后既然说到了费马小定理,我们就来说一下费马小定理在我们acm里面应用比较多的几种情况
①首先是求解逆元,这里就介绍过了
②其次就是对于a^b(mod p)的简化
即a^b(mod p)在a,p互素,且都为素数的情况下,有a^(p-1)(mod p)=1;
所以我们用b对(p-1)进行取余,因为有多少个p-1到时结果出来都是1,所以为了减少不必要的 麻烦,我们可以化简为a^(b%(p-1))(mod p)可
③最后我们可以利用费马小定理的性质来进行素数的判定,特别是对大素数的判定,
这种方法被称为Miller—Rabin素数判定
思路:我们只要一个数字a,gcd(a,p)=1,那么p为素数,如果我们找到的数字和p互质,而且同时使费马小定理成立,那么p就是一个质数,即a^(p-1)≡ 1 (mod p)成立
板子如下:
其实这个板子只是对于m-r素数判定的一个简单的抽象,而且费马小定理并不能保证反推完全正确,但是错误的概率非常低,所以大多数情况下都是可以用的,本来是要用随机数算法的,但我不会...其实随便列举几个也是可以的,20个左右就已经足够了
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll mod;
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
ll poww(ll a,ll b)
{
ll res=1,ans=a;
while(b)
{
if(b&1)
res=res*ans%mod;
ans=ans*ans%mod;
b>>=1;
}
return res;
}
int main()
{
ll f[20]={};//没有自己列......
while(~scanf("%lld",&n))
{
for(int i=0;i<20;i++)
{
if(gcd(f[i],n)==1&&poww(f[i],n-1)!=1)
break;
}
if(i==20) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
好了就到这里了....