普通的欧几里得算法
欧几里得算法是用于求两个正整数最大公约数的算法,这个算法十分基础,99%的 OIer / ACMer 都会 XD
如何求最大公约数?
一种十分朴素的方法是枚举。(以下全部默认 $a>b$)若 $a equiv 0 (mod b)$,最大公约数为b,否则从 $frac{a}{2}$ 到 $1$ 枚举试除。然而显然这种做法超时概率为1。
那么有没有快一点的做法呢?
显然是有的,伟大的欧几里得在几千年前就提出了这种做法 orz。
显然,$gcd(a, b)=gcd(b, amod b)$,于是只要这样不断递归,当$b=0$时,就可以求出最大公约数,即为a。
给出代码:
int gcd(int a, int b) { return (b == 0) ? a : gcd(b, a % b); }
证明留给读者自证。
(当然不会这样子啦QAQ)
证:
设 $a = kb + r$(a, b, k, r皆为正整数, 且r<b),则 $r = a mod b$
设 $d$ 是 $a$ 与 $b$ 的一个公约数, 即$d |a$, $d|b$。
因为$r = a - kb$, 两边同时除以 $d$, r/d=a/d-kb/d=m,由等式右边可知m为整数,因此 $d|r$
因此$d$也是 $b,a mod b$的公约数
假设$d$是$b,a mod b$的公约数, 则$d|b,d|(a-k*b),k$是一个整数。
进而$d|a$.因此$d$也是$a,b$的公约数
因此$(a,b)$和$(b,a mod b)$的公约数是一样的,其最大公约数也必然相等。
得证。
这种算法还有个比较形象的名字叫辗转相除法,时间复杂度为 $O(lg{b})$
这是如何得到的?
看这里
适合高精度的改编版
如果a和b都特别大,需要高精度怎么办?
高精度除以高精度是非常棘手的,通常没人写那种东西。所以我们需要将它转移成别的运算或高精除以int。
代码:
int gcd(int m, int n) { if (m == n) return m; if (m < n) return gcd(n, m); if (m & 1) { if (n & 1) return gcd(n, m - n); return gcd(m, n >> 1); } if (n & 1) return gcd(m >> 1, n); return gcd(m >> 1, n >> 1); }
大致就是这个意思啦,我没写高精度,不过像除以2,判断奇偶这种东西是很容易实现的。这么做就很简单了。
扩展欧几里得算法
扩欧通常是用来求$ax+by=c$这类方程的整数解。比如方程 $x+2y=3$的整数解为$x=1-2t,y=1+t ( t∈Z)$。
需要注意的是,当$c$不为 gcd(a,b) 的倍数时,方程是没有整数解的(想想看为什么?)。
通过我们平时手算丢番图方程的过程,可以发现可以这么做:(下面的这段伪代码摘自《算法导论》的31.2)
EXTENDED-EUCLID(a,b) if b==0 return(a,1,0) else(d',x',y')=EXTENDED-EUCLID(b,a mod b) (d,x,y)=(d',y',x'-[a/b]y') return(d,x,y)
这样可以求出一组解(然后就很容易得出通解)并且还顺便求出了gcd(a,b)的值——因为其实扩欧是普通辗转相除法的一点变形。
下面是exgcd的代码:
int exgcd(int a, int b, int &x, int &y) { if (!b) { x = 1; y = 0; return a; } int g = exgcd(b, a % b, x, y); x = y; y = t - a / b * y; return g; }
P.S.对于解丢番图方程、辗转相除法证,其他方法 可以看看 R·柯朗的《什么是数学?》中讲数论的那一部分,还是写得不错的。