对于整数而言:
若x能够整除y,则记x|y。称x为y的约数。
若m是x除y的余数,则记作x
m
o
d
mod
mod y=m,或x%y=m。称x被y取模的结果为m,m为模数。或称x模y等于m。
m
o
d
mod
mod就是取模运算(取余运算)。
x|y,等价于y
m
o
d
mod
mod x=0
最大公约数
若x|y,则x是y的约数。
若x|a,且x|b,则称x是a、b的公约数。
那么,所有a、b的公约数中最大的那一个,称为a、b的最大公约数(Greatest Common Divisor),记作gcd(a,b)。
一些时候简写为g,gcd,g(a,b),(a,b),…
欧几里得算法(gcd)
欧几里得算法可以快速求出两数a,b的最大公约数。
其表述如下递归式:
g
c
d
(
a
,
b
)
=
{
g
c
d
(
b
,
a
m
o
d
b
)
(
b
≠
0
)
a
(
b
=
0
)
gcd(a,b)=\left\{\begin{matrix} gcd(b,a \:mod\: b)\;(b\neq 0)\\ a\; \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;(b=0) \end{matrix}\right.
gcd(a,b)={gcd(b,amodb)(b=0)a(b=0)
证明一下:
假设此时a>=b,否则进行一轮递归之后,a>=b。
设s|a,且s|b。
则有:
a
=
k
1
s
,
b
=
k
2
s
(
k
1
,
k
2
∈
Z
)
a=k_1s,b=k_2s(k_1,k2\in \mathbb{Z})
a=k1s,b=k2s(k1,k2∈Z)
所以:
a
−
b
=
k
1
s
−
k
2
s
=
(
k
1
−
k
2
)
s
a-b=k_1s-k_2s=(k_1-k_2)s
a−b=k1s−k2s=(k1−k2)s
即:
s
∣
a
−
b
s|a-b
s∣a−b
如果a-b仍大于等于b,把a-b看做新的a,重复上面的过程,继续用b来减它。
最终得到
s
∣
a
−
k
⋅
b
(
k
∈
Z
)
s|a-k\cdot b(k\in\mathbb{Z})
s∣a−k⋅b(k∈Z),使得
a
−
k
⋅
b
<
b
a-k\cdot b<b
a−k⋅b<b,此时
a
−
k
⋅
b
=
a
m
o
d
b
a-k\cdot b=a\;mod\;b
a−k⋅b=amodb。
这很显然,如果a一直减去b,直到小于b,那么必定得到除以b的余数。
也就是说
s
∣
a
m
o
d
b
s|a\; mod\; b
s∣amodb。
这就说明任意a、b的约数都是a
m
o
d
mod
mod b的约数。
因为任意a、b的约数都是a
m
o
d
mod
mod b的约数,因此a、b的最大公约数也是a
m
o
d
mod
mod b的约数。
即
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)
QED.
就有了代码:
int gcd(int a,int b) {
if(!b) return a;
return gcd(b,a%b);
}
其中!b表示b==0.
当然也有 g c d ( a , b ) = g c d ( a , a m o d b ) gcd(a,b)=gcd(a,a\;mod\;b) gcd(a,b)=gcd(a,amodb)。可是这样如果b<a,就需要手动交换,代码写起来不方便,一般不采用这种形式。
你可以证明一下,a
m
o
d
mod
mod b <=
a
2
\frac{a}{2}
2a,这表明每递归两次,数据规模至少缩小一半。
若a、b与n的数据规模相当(同阶):欧几里得算法(gcd)的时间复杂度为O(logn)。
库中的gcd函数
在实现的时候,一般不采用手写的gcd函数。事实上,比赛的编译环境支持使用自带的gcd函数:__gcd(a,b)
__gcd(a,b)包含在algorithm库内,返回两个数的最大公约数。复杂度O(logn)。
后记
于是皆大欢喜。