普通的欧几里得算法
欧几里得算法是用于求两个正整数最大公约数的算法,这个算法十分基础,99%的 OIer / ACMer 都会 XD
如何求最大公约数?
一种十分朴素的方法是枚举。(以下全部默认
a
>
b
a>b
a>b)若
a
≡
0
(
m
o
d
  
b
)
a \equiv 0 (\mod b)
a≡0(modb),最大公约数为b,否则从
a
2
\frac{a}{2}
2a 到
1
1
1 枚举试除。然而显然这种做法超时概率为1。
那么有没有快一点的做法呢?
显然是有的,伟大的欧几里得在几千年前就提出了这种做法 orz。
显然,
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
m
o
d
  
b
)
gcd(a, b)=gcd(b, a\mod b)
gcd(a,b)=gcd(b,amodb),于是只要这样不断递归,当
b
=
0
b=0
b=0时,就可以求出最大公约数,即为a。
给出代码:
int gcd(int a, int b)
{
return (b == 0) ? a : gcd(b, a % b);
}
证明留给读者自证。
(当然不会这样子啦QAQ)
证:
设
a
=
k
b
+
r
a = kb + r
a=kb+r(a, b, k, r皆为正整数, 且r<b),则
r
=
a
m
o
d
  
b
r = a \mod b
r=amodb
设
d
d
d 是
a
a
a 与
b
b
b 的一个公约数, 即
d
∣
a
d |a
d∣a,
d
∣
b
d|b
d∣b。
因为
r
=
a
−
k
b
r = a - kb
r=a−kb, 两边同时除以
d
d
d, r/d=a/d-kb/d=m,由等式右边可知m为整数,因此
d
∣
r
d|r
d∣r
因此
d
d
d也是
b
,
a
m
o
d
b
b,a mod b
b,amodb的公约数
假设
d
d
d是
b
,
a
m
o
d
b
b,a mod b
b,amodb的公约数, 则
d
∣
b
,
d
∣
(
a
−
k
∗
b
)
,
k
d|b,d|(a-k*b),k
d∣b,d∣(a−k∗b),k是一个整数。
进而
d
∣
a
d|a
d∣a.因此
d
d
d也是
a
,
b
a,b
a,b的公约数
因此
(
a
,
b
)
(a,b)
(a,b)和
(
b
,
a
m
o
d
b
)
(b,a mod b)
(b,amodb)的公约数是一样的,其最大公约数也必然相等。
得证。
这种算法还有个比较形象的名字叫辗转相除法,时间复杂度为
O
(
lg
b
)
O(\lg{b})
O(lgb)
这是如何得到的?
看这里
适合高精度的改编版
如果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,判断奇偶这种东西是很容易实现的。这么做就很简单了。
扩展欧几里得算法
扩欧通常是用来求
a
x
+
b
y
=
c
ax+by=c
ax+by=c这类方程的整数解。比如方程
x
+
2
y
=
3
x+2y=3
x+2y=3的整数解为
x
=
1
−
2
t
,
y
=
1
+
t
(
t
∈
Z
)
x=1-2t,y=1+t ( t∈Z)
x=1−2t,y=1+t(t∈Z)。
需要注意的是,当
c
c
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·柯朗的《什么是数学?》中讲数论的那一部分,还是写得不错的。