扩展欧几里得算法
用来在已知整数a,b的情况下求解符合条件的x,y值,满足等式ax+by=gcd(a,b)。
且有对于整数 a,b, 必然存在整数对 x,y,满足ax+by=gcd(a,b)。
有关算法的递归思想
由欧几里得算法可知求解两个整数gcd的过程,即gcd(a,b)=gcd(b,a%b)。那么当辗转到最终状态时b=0,a=gcd,此时对于ax+by=gcd(a,b)对应着x=1,y=0,即a*1+0*y=gcd(a,b) (其实此时y的取值是无关紧要的,毕竟y的系数已经化为0)。当然这只是最终状态下x,y的取值,那么我们应该怎样由最终的状态反推到之前的状态呢
假设我们当前已经进入gcd(b,a%b)的化简状态,则有b*x+(a%b)*y=gcd,
可化简为 b*x+(a-(a/b)*b)*y=gcd,(a/b为下取整)
继续化简 b*x+a*y-(a/b)*b*y=gcd,
则可得 a*y+b*(x-(a/b)*y)=gcd
对比于之前的等式 ax+by=gcd 我们不难发现 x 与 y 之间的变换关系,即x=y,y=x-(a/b)*y,由此关系进行递归即可。
有关解的增长周期关系
对于不定方程ax+by=gcd(a,b),肯定是存在多组解的,使用扩展欧几里得算法处理得到x0,y0的只是一组特解,那么我们应该怎样用x0,y0去表示通解从而方便我们求得符合题意要求的解呢
ax+by=gcd 可化简为 y=
−ba∗x+gcd
,由该方程我们可知,x 每增加 m,y 值便减少
ba∗m
,一方面因为我们需要求解整数解,所以 m 和
ba∗m
必须是整数,另一方面我们需要尽可能的表示出 x,y 解的最大范围,所以 m 必须取尽可能小的值,其对应的
ba∗m
也相应最小。所以可得 m=
bgcd
,此时x和y的增长周期分别为
bgcd
和
agcd
,两个数互为素数,满足最小整数值的要求。则有通解
x=x0+bgcd,y=y0−agcd
。
算法应用
1、求解不定方程
对于方程ax+by=c方程的求解,只需要由上述概述加上一步的转化
即在ax+by=gcd的等式两端同时乘上
cgcd
,那么对于ax+by=gcd的特解x0也需要乘上
cgcd
才是ax+by=c方程的特解, 所以当c%gcd!=0时,是属于无解情况,关于通解的表示与上述还是一样的。
POJ 2115 C Looooops
2、求解模线性方程(线性同余方程)
(1)一元线性同余方程组
以POJ 2891为例
先由两个方程说起, 对于M%a1=r1
M%a2=r2两个关系式可以转化为
a1*x+r1=M
a2*x+r2=M
两式相减便有:a1*x-a2*y=r2-r1
对于该方程使用扩展欧几里得算法可以得到一个特解x0,则X可由一个满足这两个方程的值,即x=x0*a1+r1。
且X的通解可以表示为:X=x+k*lcm(a1,a2)
即 lcm(a1,a2)*z+x=X 现在则实现了将两个方程合并,则合并后新的方程的 a1’=lcm(a1,a2)=a1(a2/gcd),r1’=x=x0*a1+r1。
以此类推,与剩下的n-2个方程进行合并求出最终解。
代码实现
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll __int64
int n;
ll exgcd(ll a,ll b,ll &x,ll &y){
if(!b){
x=1,y=0;
return a;
}
ll ans=exgcd(b,a%b,y,x);
y-=a/b*x;
return ans;
}
void solve()
{
ll a1,r1,a2,r2,a,b,c,t;
ll d,x0,y0;
bool flag=1;
scanf("%lld %lld",&a1,&r1);
for(int i=1; i<n; i++)
{
scanf("%lld %lld",&a2,&r2);
a=a1,b=a2,c=r2-r1;
d=exgcd(a,b,x0,y0);
if(c%d!=0)
{
flag=0;
}
ll t=b/d;
x0=(x0*(c/d)%t+t)%t;
r1=a1*x0+r1;
a1=a2/d*a1;
r1%=a1;
}
if(!flag)
{
printf("-1\n");
return ;
}
if(r1<0)
r1+=a1;
printf("%lld\n",r1);
}
int main()
{
while(~scanf("%d",&n))
solve();
return 0;
}
(2)多元线性同余方程组
后序补更……
3、求解模的逆元
有关乘法逆元,满足a*x%m=1,即ax+my=1,即为求解不定方程c=1的情况
题目中一般会要求输出最小的正整数,因为最后结果是对 m 取模,所以当题意中的 m 有可能是负数时,要先对 m 取绝对值,且通解可以表示为 X=x0+(m/gcd)*t=x0+m*t(gcd必为1,否则c%gcd!=0无解)。当x0是非正数时,对 m 取模仍然是非正数,再加上 m 即可。
ZOJ 3609 Modular Inverse
#include <iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
ll d;
ll ex_gcd(ll a,ll b,ll &x,ll &y)
{
if(!b)
{
d=a,x=1,y=0;
}
else
{
ex_gcd(b,a%b,y,x);
y-=(a/b)*x;
}
return d;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
ll a,m,x,y;
scanf("%lld %lld",&a,&m);
ll gcd=ex_gcd(a,m,x,y);
if(gcd!=1)
printf("Not Exist\n");
else
{
m=abs(m);
x%=m;
if(x<=0)
x+=m;
printf("%lld\n",x);
}
}
return 0;
}