第一次去清北学堂就学了扩欧,当时听得很懵,时隔将近两个月又把课件翻出来琢磨,终于明白了,做一下笔记,写的一塌糊涂不一定都对。
众所周知 老师告诉我们,一定存在整数对(x,y)使得ax+by=gcd(a,b)。扩欧就是来求x和y。怎么求呢?既然叫做【扩展】欧几里得,那肯定是跟欧几里得算法有联系了,只不过在原有算法上“稍加修改”…
补一下欧几里得算法的代码:
int gcd(int a,int b)
{
if(b==0) return a;
else return gcd(b,a%b);
}
欧几里得算法的最终状态就是a=gcd(a,b),b=0。有没有恍然大悟?我们只要令x=1,y=0,显然ax+by=gcd(a,b)。那么问题来了,如何从这一个状态往前推出前一个状态呢?
网上有很多推到给出了公式,但我喜欢颓废 我觉得自己感受过程比死板的公式更易理解。我觉得zhw老师举的这个栗子很好的帮我理解了这个问题。
举栗子之前,首先明确一个众所周知的事实:a%b=a-a/b*b
我们来看栗子:
比如12和7,用朴素欧几里得的过程是这样的:
gcd(12,7)=gcd(7,5)=gcd(5,2)=gcd(2,1)=gcd(1,0)=1。
如果我们倒着推:
末状态:1*1+0*0=1
即1*1+0*(2-2*1)=1 -> 0*2+1*1=1
即0*2+1*(5-2*2)=1 -> 1*5-2*2=1
即1*5-2*(7-1*5)=1 -> -2*7+3*5=1
即-2*7+3*(12-1*7)=1 -> 3*12-5*7=1
求得x=3 y=-5
意不意外?激不激动?如果把当前状态设为x1,y1,a,b,后一个状态设为x2,y2,那么前一个状态可以由后一个状态推导得来:x1=y2,y1=x2-a/b*y2。
这样就可以皆大欢喜地改进欧几里得算法了!
int exgcd(int a,int b,int &x,int &y) //直接取x,y的地址,直接修改
{
if(b==0)
{
x=1;y=0;
return a;
}
int ans=exgcd(b,a%b,x,y); //ans存储gcd
int tmp=x;
x=y;
y=tmp-a/b*y;
return ans;
}
如果a*b=1 (mod p),我们就称b是a模p意义下的乘法逆元。
定理:a存在模p的乘法逆元的充要条件是gcd(a,p) = 1。
反证法还是比较好想的,假设gcd(a,p)!=1,那么a*b一定也含有gcd(a,p),就不可能等于1了。
所以我们可以用扩欧求出一组x,y,满足ax+by=1。
可是怎么用扩欧求逆元?看起来多了一项啊。
模掉!(真暴力)
ax+by=1在模b意义下“by”这一项不就没了嘛!
这样,x就可以看做是a模b意义下的乘法逆元。
就可以用扩欧了!
int inver(int a,int p)
{
int x,y;
int gcd=exgcd(a,p,x,y);
if(gcd!=1) return -1; //若a,p不互质则无解
int ans=(x%p+p)%p; //x可能是负的
return ans;
}
ps:
在p是质数的情况下求逆元还可以用费马小定理!
费马小定理为: a^(p-1)=1 (mod p) (p为质数)
所以,a的逆元就是a^(p-2) mod p,快速幂即可求出。