今天刚学扩展欧几里得算法,打算趁热写篇博客回顾一下,顺便防止以后自己忘记了。
话不多说。
首先请出欧几里得算法,还是非常简洁明了的。
int gcd(int a, int b)
{
if (b == 0)
{
return a;
}
return gcd(b, a % b);
}
顺便提一句,欧几里得算法不需要考虑a和b的大小,因为假如a<b,会return (b,a%b);a%b=a,相当于交换了一次,因此不需要考虑大小。(以前老是忘记,一直考虑大小问题)
然后就要写扩展欧几里得算法了,先整个问题引出扩展欧几里得算法。
假如我们要求ax+by=c的一组解,也就是求x,y,这时候我们就需要用到扩展欧几里得算法了。
首先我们还需要一个定理:
ax+by=c。c必须为gcd(a,b)的倍数。(证明我还是不写了,不太会,而且我觉得下面的过程看完了也大概能明白为什么c必须是gcd的倍数了)
接着举个例子好了;
a=78,b=51,c=3.
根据gcd可以推出过程为:
78 51 | 51 * 1+27=78
51 27 | 27 * 1+24=51
27 24 | 24 * 1+3=27
24 3 | 3 * 8=24
3 0 |
至此我们可以算出gcd(78,51)=3;
这时候有人就问了,这有什么意义吗?跟c也没任何关系啊,这时候我们就要引出他们之间的关系了.
根据241+3=27。我们可以得到3=27-24 * 1,这说明27和24就是3的一个线性组合。
根据271+24=51。我们可以得到3=27-(51-27)*1=(-51)+27 * 2,这说明51和27也是3的一个线性组合。
如下,我们可以这样的式子。
3=27-24 * 1=(-51)+27 * 2=78 * 2-51 * 3。
实际上,最后的2和(-3)正是我们需要的一组解。那我们应该怎么将这个过程表示出来呢?
就是通过求bx0+(a mod b)*y0=c关联ax+by=c。
a mod b=a-b * (a/b)。于是我们可以得到这样一个方程组。
x=y0。
y=x0-(a/b) y0是不是有辗转相除法内味了。
于是乎我们就可以引出扩展欧几里得算法了。
int exgcd(int a, int b, int x, int y)
{
if (b == 0)
{
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
int x0, y0;
x0 = x;
y0 = y;
x = y0;
y = x0 - (a / b) * y0;
return d;
}
当然我们还可以进一步化简代码,利用一下c++的引用。
下面放出代码:
int exgcd(int a, int b, int& x, int& y)
{
if (b == 0)
{
x = 1; y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);//这里x和y交换了一下位置,需要注意一下
y -= (a / b) * x;
return d;
}
其实和上面差不多,但是稍微精简了一下,如果看不懂意思,可以看我的下面的分析:
根据d=exgcd(b,a%b,y,x)可以得到y=x0,x=y0。因此实际上x已经等于y0了,之后就是y-=(a/b)* x;实际上等于y=y-(a/b) * x;此时的y=x0,x=y0,与上面的y=x0-(a/b)*y0实际上是完全一样的。
最后还有另一个问题,就是我们这里讨论的c正好是78和51的最大公因数,假如不是最大公因数,而是最大公因数的倍数,那该咋办呢?
那我们只需要把ax+by=k * gcd(a/b);改成a * k*x‘+b * k * y’=k * gcd(a/b)即可。
这样我们就成功求出了一组解。
那我们还想要其他的解,要咋办呢?
我们设x1=x+f以及y1我们可以得到a * (x1-f)+by=c。然后将(x1-f)从括号里拆出来最后再合并可以得到ax1+b(y-af/b)=c。
因此我们可以得到一个方程组
x1=x+f
y1=y-a*f/b
我们还需要保证f和af/b为整数,并且后者要等于a’f/b’,其中a’=a/gcd(a,b),b’=b/gcd(a,b)。因为a’与b’互质,因此f=k * b’。
最后得到
xk=x+k * b/gcd(a,b)
yk=y-k * a/gcd(a,b)
的方程组。
怎么说呢(其实斜体这一小段是从我学习的那个博客那边照搬过来的,因为我还不是特别能理解)
至此我们通解也可以求出来了,最后想说一下扩展欧几里得算法的应用,但我现在也没刷过这种题,只能先引用一下别人认为的应用有哪些了,之后有时间我回来补一下。
1求解不定方程
2求解模线性方程(线性同余方程)
3求解模的逆元