深入解析:【数论】最大公因数 (gcd) 与最小公倍数 (lcm)
文章目录
一、最大公因数 (gcd)
1. 最大公因数的性质
我们在小学的时候就学过最大公因数 (Greatest Common Divisor, 简称 GCD),就是两个或多个数所有公因数中最大的那一个。比如 12 12 12 和 18 18 18 的最大公因数是 6 6 6。我们通常把两个数 x x x 和 y y y 的最大公因数记为 g c d ( x , y ) gcd(x, y) gcd(x,y) 或者 ( x , y ) (x, y) (x,y)。你也许学过短除法、辗转相除法来求最大公因数,但是如果落实到代码上,该如何写呢?首先我们需要了解最大公因数的几个性质:
对于任意整数 a a a, b b b, c c c,有
( a , b ) = ( b , a ) = ( − a , b ) = ( a , − b ) (a, b)=(b, a)=(-a, b)=(a, -b) (a,b)=(b,a)=(−a,b)=(a,−b);
若 a ∣ b a \mid b a∣b(" ∣ \mid ∣ " 为整除符号,意为 b b b 能够被 a a a 整除),则 ( a , b ) = a (a, b)=a (a,b)=a;
对于任意两个整数 x x x, y y y,必有 ( a , b ) ∣ a x + b y (a, b)\mid ax + by (a,b)∣ax+by;
设 x x x 是 a a a 和 b b b 的任意公因数,则有 x ∣ ( a , b ) x \mid (a, b) x∣(a,b);
若 a = b q + c a = bq + c a=bq+c, q q q 是一个整数,则有 ( a , b ) = ( b , c ) (a, b) = (b, c) (a,b)=(b,c)。
其中第 5 个性质是我们用代码求解 gcd 的核心,下面对它进行证明:
证明:
(
a
,
b
)
(a, b)
(a,b) 是
a
a
a 和
b
b
b 的最大公因数,那么一定有
(
a
,
b
)
∣
a
(a, b) \mid a
(a,b)∣a
以及
(
a
,
b
)
∣
b
(a, b) \mid b
(a,b)∣b
因为
a
=
b
q
+
c
a = bq + c
a=bq+c,所以
c
=
a
−
b
q
c = a - bq
c=a−bq,又由性质 3 可知,两个数的最大公因数可以整除这两个数的线性组合,所以有
(
a
,
b
)
∣
a
−
b
q
(a, b) \mid a - bq
(a,b)∣a−bq
即
(
a
,
b
)
∣
c
(a, b) \mid c
(a,b)∣c
由于
(
a
,
b
)
(a, b)
(a,b) 既是
b
b
b 的公因数,又是
c
c
c 的公因数,所以
(
a
,
b
)
(a, b)
(a,b) 是
b
b
b 和
c
c
c 的一个公因数,由性质 4 可得
(
a
,
b
)
∣
(
b
,
c
)
(a, b) \mid (b, c)
(a,b)∣(b,c)
同理可以用
a
=
b
q
+
c
a = bq + c
a=bq+c 求得
(
b
,
c
)
∣
(
a
,
b
)
(b, c) \mid (a, b)
(b,c)∣(a,b)
由于
(
a
,
b
)
(a, b)
(a,b) 和
(
b
,
c
)
(b, c)
(b,c) 相互整除,所以有
(
a
,
b
)
=
(
b
,
c
)
(a, b) = (b, c)
(a,b)=(b,c)
证毕。
2. 欧几里得算法
欧几里得算法,其实就是我们中学学过的辗转相除法,我们先来看一个例子:求 ( 4864 , 3458 ) (4864, 3458) (4864,3458)。
我们先用
4864
4864
4864 除以
3458
3458
3458,得到一个商和余数:
4864
=
3458
×
1
+
1406
4864 = 3458\times 1 + 1406
4864=3458×1+1406
由上面我们所证明的性质 5,
(
4864
,
3458
)
(4864, 3458)
(4864,3458) 就转换成了求
(
3458
,
1406
)
(3458, 1406)
(3458,1406)。
接着我们用
3458
3458
3458 去除
1406
1406
1406,得到新的商和余数:
3458
=
1406
×
2
+
646
3458 = 1406 \times 2 + 646
3458=1406×2+646
现在就转换成了求
(
1406
,
646
)
(1406, 646)
(1406,646)。
像这样反复运算,我们的余数最终会变成 0,而我们所算出的最后一个非零余数就是最大公因数。这里的最后一个非零余数是 38 38 38,所以最大公因数是 38 38 38。
观察上面的计算过程我们不难发现,这中间的计算方法都是一样的,因此我们可以用递归来实现:
// 求两个数 x 和 y 的最大公因数
int gcd(int x, int y)
{
if(y == 0) return x;
return gcd(y, x % y);
}
二、最小公倍数 (lcm)
最小公倍数 (Least Common Multiple, 简称 LCM),就是两个或多个数所有公倍数中最小的那个。比如 8 8 8 和 12 12 12 的最小公倍数是 24 24 24。我们通常把两个数 x x x 和 y y y 的最小公倍数记为 l c m [ x , y ] lcm[x, y] lcm[x,y] 或者 [ x , y ] [x, y] [x,y]。
两个数的最小公倍数的求法十分简单,只需要计算出两个数的最大公因数
(
a
,
b
)
(a, b)
(a,b),那么就有
[
a
,
b
]
=
a
×
b
(
a
,
b
)
[a, b] = \frac{a \times b}{(a, b)}
[a,b]=(a,b)a×b
三、练习
1. 最大公因数和最小公倍数 ⭐
【题目链接】
【题目描述】
给定两个正整数 a , b a,b a,b,求他们的最大公约数(gcd)和最小公倍数(lcm)。这两个整数 a , b a,b a,b 均在 int 范围内。
【输入格式】
两个整数 a a a 和 b b b,用空格分隔。
【输出格式】
两个整数表示答案,用空格隔开。
【示例一】
输入
6 15输出
3 30
#include<iostream>
using namespace std;
typedef long long LL;
// 将 a 和 b 的类型设置为 long long 防止溢出
LL gcd(LL a, LL b)
{
return b == 0 ? a : gcd(b, a % b);
}
int main()
{
LL a, b;
cin >> a >> b;
LL g = gcd(a, b);
cout << g << ' ' << a * b / g << endl;
return 0;
}
2. 三个数的最大公因数 ⭐
【题目链接】
[B3736 信息与未来 2018] 最大公约数 - 洛谷
【题目描述】
输入三个正整数 x , y , z x,y,z x,y,z,求它们的最大公约数(Greatest Common Divisor) g g g:最大的正整数 g ≥ 1 g ≥1 g≥1,满足 x , y , z x,y,z x,y,z 都是 g g g 的倍数,即 ( x m o d g ) = ( y m o d g ) = ( z m o d g ) = 0 (x \bmod g) = (y \bmod g) = (z \bmod g) = 0 (xmodg)=(ymodg)=(zmodg)=0。
【输入格式】
输入一行三个正整数 x , y , z x,y,z x,y,z。
【输出格式】
输出一行一个整数 g g g,表示 x , y , z x,y,z x,y,z 的最大公约数。
【示例一】
输入
12 34 56输出
2
【示例二】
输入
28 70 28输出
14
【说明/提示】
样例 1 1 1
12 = 2 × 6 , 34 = 2 × 17 , 56 = 2 × 28 , g = 2 12 = 2 × 6, 34 = 2 × 17, 56 = 2 × 28, g = 2 12=2×6,34=2×17,56=2×28,g=2。
样例 2 2 2
28 = 14 × 2 , 70 = 14 × 5 , 28 = 14 × 2 , g = 14 28 = 14 × 2, 70 = 14 × 5, 28 = 14 × 2,g = 14 28=14×2,70=14×5,28=14×2,g=14。
数据规模
所有数据满足 1 ≤ x , y , z ≤ 1 0 6 1 ≤ x,y,z ≤ 10^6 1≤x,y,z≤106。
本题原始满分为 15 pts 15\text{pts} 15pts。
只需算出其中任意两个数的最大公因数,再用这个最大公因数去和另一个数求最大公因数即可。
#include<iostream>
using namespace std;
int gcd(int x, int y)
{
if(y == 0) return x;
return gcd(y, x % y);
}
int main()
{
int x, y, z;
cin >> x >> y >> z;
cout << gcd(gcd(x, y), z) << endl;
return 0;
}
3. 有大整数的最大公因数 ⭐⭐
【题目链接】

(1) 补充:秦九韶算法
对于一个
n
n
n 次多项式:
P
(
x
)
=
a
n
x
n
+
a
n
−
1
x
n
−
1
+
⋯
+
a
1
x
+
a
0
P(x)=a_nx^n+a_{n−1}x^{n−1}+⋯+a_1x+a_0
P(x)=anxn+an−1xn−1+⋯+a1x+a0
可以改写为:
P
(
x
)
=
(
⋯
(
(
a
n
x
+
a
n
−
1
)
x
+
a
n
−
2
)
x
+
⋯
+
a
1
)
x
+
a
0
P(x)=(⋯((a_nx+a_{n−1})x+a_{n−2})x+⋯+a_1)x+a_0
P(x)=(⋯((anx+an−1)x+an−2)x+⋯+a1)x+a0
比如
3
×
2
4
+
5
×
2
3
+
6
×
2
2
+
4
×
2
+
1
3 \times2^4 + 5\times 2^3 + 6\times 2^2 + 4 \times 2 + 1
3×24+5×23+6×22+4×2+1
根据秦九韶算法,可以写成
(
(
(
3
×
2
+
5
)
×
2
+
6
)
×
2
+
4
)
×
2
+
1
(((3\times2 + 5)\times2 + 6)\times2 + 4)\times2 + 1
(((3×2+5)×2+6)×2+4)×2+1
时间复杂度为
O
(
n
)
O(n)
O(n)。
(2) 解题思路
那么对于这道题来说,由于
a
a
a 可能非常大,我们只能用 string 字符串来存储它。而我们要计算
(
a
,
b
)
(a, b)
(a,b),只需计算
(
b
,
a
m
o
d
b
)
(b, a \bmod b)
(b,amodb) 即可,问题就转换成了计算
a
m
o
d
b
a \bmod b
amodb。对于一个数字
a
a
a,比如它是
1234
1234
1234,那么我们可以把它写成
1
×
1
0
3
+
2
×
1
0
2
+
3
×
10
+
4
1\times10^3 + 2\times10^2 + 3\times10 + 4
1×103+2×102+3×10+4
利用秦九韶算法,可以写成
(
(
1
×
10
+
2
)
×
10
+
3
)
×
10
+
4
((1\times10 + 2)\times10 + 3)\times 10 + 4
((1×10+2)×10+3)×10+4
这样我们就可以只对数字的每一位进行操作,这符合字符串的操作模式。我们只需从前往后取出每一位的数字,将它乘
10
10
10 再加上后一位,这样一边计算一边取模,最终得到的结果就是
a
m
o
d
b
a \bmod b
amodb 的结果。
#include<iostream>
using namespace std;
int gcd(int x, int y)
{
if(y == 0) return x;
return gcd(y, x % y);
}
int main()
{
string sa;
int b;
cin >> sa >> b;
// 防止 a * 10 溢出
long long a = sa[0] - '0';
for(int i = 1; i < sa.size(); i++)
{
a = (a * 10 + sa[i] - '0') % b;
}
cout << gcd(b, a) << endl;
return 0;
}

浙公网安备 33010602011771号