辗转相除法
本文内容主要引用自《挑战程序设计竞赛》
1.求最大公约数
现在给定我们两个整数\(a\)与\(b\),要求我们求出两者的最大公约数。最简单的思路就是从\(1\)枚举到\(min(a,b)\),但是效率较低,因此我们就需要一种更快速的算法,即辗转相除法。
设函数\(gcd(a,b)\)是求取\(a,b\)的最大公约数,假设\(a\)除以\(b\)得到的商和余分别为\(p\)和\(q\)。因为有\(a=p*b+q\),因此我们设\(m=gcd(b,p)\),由等式右边可以知道\(m\mid a\)。反之,我们有\(q=a-b*p\),两边同除以\(m\),我们知道得到的同样是一个整数,因此\(m\mid q\)。从而知道\(gcd(b,a\bmod b)=gcd(a,b)\)。不断这样操作下去,由于\(gcd\)函数中的第二个参数总是不断变小,最终会得到\(gcd(a,b)=gcd(c,0)\)。而\(0\)和\(c\)的最大公约数是\(c\),所以\(gcd(c,0)=c\),这样就得到了\(gcd(a,b)\)。
辗转相除法的程序实现如下:
代码
int gcd(int a,int b)
{
if(b==0) return a;
return gcd(b,a%b);
}
辗转相除法时间复杂度的粗略估计:假设\(a\)和\(b\)是两个自然数,如果\(b>a\),那么我们就会有\(gcd(b,a%b)=gcd(b,a)\),经过一次递归后就变成了\(a>b\),因此我们不妨假设\(a>b\),此时函数就会按照\(gcd(a,b) \to gcd(b,a\bmod b)\to gcd(a\bmod b,b\bmod (a\bmod b))\)这样递归下去。当\(b>a/2\)时我们有\(a\bmod b=a-b<a/2\),当\(b<a/2\)时,则有\(a\bmod b<b<a/2\),于是经过两次递归后,第一个参数会小于原来的一半,所以其时间复杂度在\(O(log(max(a,b))\)以内。
2.拓展欧几里得算法
现在我们来考虑解决一个方程\(ax+by=1\)。可以发现,如果\(gcd(a,b)!=1\),那么这个方程就是无解的,反之,如果\(gcd(a,b)=1\),就可以通过拓展上面的辗转相除法来求解。通过推广我们知道,一定存在整数对\((x,y)\)使得\(ax+by=gcd(a,b)\),并可以通过相同的算法来解决。
现设int exgcd(int a,int b,int &x,int &y)
是求解该方程的函数,其返回值是\(gcd(a,b)\),与\(gcd\)一样,我们可以递归地定义\(exgcd\)。假设已经求得了\(b\cdot x^\prime+(a\bmod b)y^\prime=gcd(a,b)\)的整数解\(x^\prime\)和\(y^\prime\)。再将\(a\bmod b=a-(a/b)\cdot b\)带入就会得到\(a\cdot y^\prime+b\cdot(x^\prime-(a/b)\cdot y^\prime)=gcd(a,b)\)。则当\(b=0\)时,就有\(a\cdot 1+b\cdot 0=a=gcd(a,b)\)。将上述数学公式转换成代码就是:
代码
int exgcd(int a,int b,int &x,int &y)
{
int d=a;
if(b==0)
{
x=1,y=0;
return a;
}
d=exgcd(b,a%b,y,x);
y-=(a/b)*x;
return d;
}