最大公约数和最小公倍数
1. 基础概念
1.1 质数的定义
质数(Prime Number)是一个重要的数学概念,它的定义如下:
如果一个大于1的自然数只有1和它本身两个因数,那么这个数就被称为质数(或素数)。
换句话说,对于一个质数 p p p,它满足以下条件:
p p p 是一个大于1的自然数;
如果 p p p 能被一个小于 p p p 且大于1的自然数整除,那么 p p p 就不是质数。
根据定义,我们可以得到一些质数的例子:
2是最小的质数,因为它只有1和2两个因数;
3、5、7、11、13、17、19等都是质数;
4、6、8、9、10、12等都不是质数,因为它们除了1和本身之外,还有其他因数。
值得注意的是,1不是质数,因为质数的定义要求一个数必须大于1。同时,负整数和0也不是质数。
在数论中,质数有许多重要的性质和应用,例如:
任何一个大于1的自然数都可以唯一地表示为质数的乘积(质因数分解);
两个数的最大公约数可以通过它们的质因数分解求得;
质数的分布有一定的规律,但至今没有一个简单的公式可以生成所有的质数。
理解质数的定义是学习数论的基础,在探索最大质因数和最小质因数时,我们会频繁用到质数的概念。
1.2 合数的定义
合数(Composite Number)是与质数相对的一个概念,它的定义如下:
如果一个大于1的自然数除了1和它本身之外,还有其他因数,那么这个数就被称为合数。
换句话说,对于一个合数 c c c,它满足以下条件:
c c c 是一个大于1的自然数;
存在一个小于 c c c 且大于1的自然数 d d d,使得 d d d 能整除 c c c。
根据定义,我们可以得到一些合数的例子:
4是最小的合数,因为它有1、2和4三个因数;
6、8、9、10、12、14、15、16等都是合数;
2、3、5、7、11、13、17、19等都不是合数,因为它们只有1和本身两个因数。
值得注意的是,1既不是质数也不是合数,因为合数的定义要求一个数必须大于1。同时,负整数和0也不是合数。
质数和合数是互补的概念,它们构成了所有大于1的自然数的两个不相交的子集。换句话说:
任何一个大于1的自然数要么是质数,要么是合数;
没有一个数既是质数又是合数;
1既不是质数也不是合数。
在学习最大质因数和最小质因数时,我们主要关注的是合数,因为质数只有1和本身两个因数,而合数的因数结构更加复杂,需要进行质因数分解等操作。
1.3 因数的定义
因数(Factor)是一个基本的数学概念,它描述了两个数之间的整除关系。因数的定义如下:
如果一个整数 a a a 能被另一个整数 b b b 整除,那么我们称 a a a 是 b b b 的因数,也称 b b b 是 a a a 的倍数。
换句话说,对于两个整数 a a a 和 b b b,如果存在一个整数 k k k,使得 a × k = b a \times k = b a×k=b,那么我们说:
a a a 是 b b b 的因数;
b b b 是 a a a 的倍数;
b b b 能被 a a a 整除;
a a a 能整除 b b b。
根据定义,我们可以得到一些因数的例子:
1和6都是6的因数,因为 1 × 6 = 6 1 \times 6 = 6 1×6=6 和 2 × 3 = 6 2 \times 3 = 6 2×3=6;
1、2、3、4、6、12都是12的因数;
对于任意整数 a a a,1和 a a a 都是 a a a 的因数,因为 1 × a = a 1 \times a = a 1×a=a。
值得注意的是,因数可以是正整数、负整数或者0:
如果 a a a 是 b b b 的因数,那么 − a -a −a 也是 b b b 的因数,因为 ( − a ) × ( − k ) = b (-a) \times (-k) = b (−a)×(−k)=b;
0是0的因数,因为 0 × k = 0 0 \times k = 0 0×k=0 对于任意整数 k k k 都成立;
0不是其他任何非零整数的因数,因为 0 × k = 0 ≠ a 0 \times k = 0 \neq a 0×k=0=a 对于任意非零整数 a a a 和任意整数 k k k 都成立。
在学习最大质因数和最小质因数时,我们主要关注正整数的因数。对于一个合数,我们通常需要找到它的所有因数,或者找到它的质因数(即那些是质数的因数)。因数的概念是理解质因数分解的基础。
1.3.1 天秀的苹果
天秀有 n n n 个苹果,她想把这些苹果分成两部分,分别给她的朋友小红和小明。天秀希望小红和小明得到的苹果数量之差的绝对值尽可能小。
请你帮助天秀计算,她应该如何分配苹果,才能使得小红和小明得到的苹果数量之差的绝对值最小。
输入格式
输入一个正整数 n n n,表示天秀有 n n n 个苹果。
输出格式
输出一个整数,表示小红和小明得到的苹果数量之差的最小绝对值。
样例输入1
10
样例输出1
0
样例解释1
天秀可以将10个苹果平均分成两部分,小红和小明各得到5个苹果,它们的差的绝对值为0。
样例输入2
7
样例输出2
1
样例解释2
天秀可以将7个苹果分成3个和4个两部分,小红和小明分别得到3个和4个苹果,它们的差的绝对值为1。这是差的绝对值最小的分配方案。
数据范围
对于所有评测用例, 1 ≤ n ≤ 1 0 9 1 \leq n \leq 10^9 1≤n≤109。
题目分析:
要使小红和小明得到的苹果数量之差的绝对值最小,我们应该尽量平均地分配苹果。
如果苹果数量 n n n 是偶数,我们可以将苹果平均分成两部分,每部分都有 n 2 \frac{n}{2} 2n 个苹果,它们的差的绝对值为0。
如果苹果数量 n n n 是奇数,我们无法恰好平分苹果。最佳的分配方案是将苹果分成 n − 1 2 \frac{n-1}{2} 2n−1 个和 n + 1 2 \frac{n+1}{2} 2n+1 个两部分,它们的差的绝对值为1。
因此,我们可以得到以下结论:
- 如果 n n n 是偶数,小红和小明得到的苹果数量之差的最小绝对值为0。
- 如果 n n n 是奇数,小红和小明得到的苹果数量之差的最小绝对值为1。
我们可以用数学公式表示为:
a n s = n ans = n \\% 2 ans=n
其中 a n s ans ans 表示小红和小明得到的苹果数量之差的最小绝对值, \\% 表示取余运算。
时间复杂度分析:
该算法只需要进行一次取余运算,时间复杂度为
O
(
1
)
O(1)
O(1)。
空间复杂度分析:
该算法只需要常数级别的额外空间,空间复杂度为
O
(
1
)
O(1)
O(1)。
代码实现:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
cout << n % 2 << endl;
return 0;
}
该代码先读入苹果数量 n n n,然后输出 n % 2 n \% 2 n%2 的结果,即小红和小明得到的苹果数量之差的最小绝对值。
1.3.2 天秀的因数个数
天秀有一个正整数 n n n,她想知道 n n n 有多少个正因数。
正整数 a a a 是正整数 b b b 的因数,当且仅当存在整数 k k k,使得 b = a × k b = a \times k b=a×k。
请你帮助天秀计算 n n n 的正因数的个数。
输入格式
输入一个正整数 n n n。
输出格式
输出一个整数,表示 n n n 的正因数的个数。
样例输入1
12
样例输出1
6
样例解释1
12 的正因数有 1、2、3、4、6、12,共有 6 个。
样例输入2
36
样例输出2
9
样例解释2
36 的正因数有 1、2、3、4、6、9、12、18、36,共有 9 个。
数据范围
对于所有评测用例, 1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1≤n≤106。
题目分析:
要计算正整数
n
n
n 的正因数的个数,我们可以枚举从 1 到
n
n
n 的所有正整数,判断每个数是否为
n
n
n 的因数。
如果正整数 i i i 是 n n n 的因数,那么 n ÷ i n ÷ i n÷i 一定是一个整数,即 n % i = 0 n \% i = 0 n%i=0。
因此,我们可以用以下算法计算 n n n 的正因数的个数:
- 初始化计数器 c n t = 0 cnt = 0 cnt=0。
- 从 1 枚举到
n
n
n,对于每个数
i
i
i:
- 如果 n % i = 0 n \% i = 0 n%i=0,说明 i i i 是 n n n 的因数,将 c n t cnt cnt 加 1。
- 输出 c n t cnt cnt 的值。
时间复杂度分析:
该算法需要枚举从 1 到
n
n
n 的所有数,时间复杂度为
O
(
n
)
O(n)
O(n)。
空间复杂度分析:
该算法只需要常数级别的额外空间,空间复杂度为
O
(
1
)
O(1)
O(1)。
代码实现:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (n % i == 0) {
cnt++;
}
}
cout << cnt << endl;
return 0;
}
该代码先读入正整数 n n n,然后从 1 枚举到 n n n,对于每个数 i i i,判断 n % i n \% i n%i 是否为 0。如果为 0,说明 i i i 是 n n n 的因数,将计数器 c n t cnt cnt 加 1。最后输出 c n t cnt cnt 的值,即 n n n 的正因数的个数。
解法二
题目分析:
除了枚举从 1 到
n
n
n 的所有数之外,我们还可以利用因数的对称性来优化算法。
对于正整数 n n n 的任意因数 i i i,如果 i × j = n i \times j = n i×j=n,那么 j j j 也一定是 n n n 的因数。
因此,我们可以只枚举从 1 到 n \sqrt{n} n 的所有数,对于每个因数 i i i,再判断 n i \frac{n}{i} in 是否也是 n n n 的因数。这样可以避免重复计算,提高算法效率。
具体算法如下:
- 初始化计数器 c n t = 0 cnt = 0 cnt=0。
- 从 1 枚举到
n
\sqrt{n}
n,对于每个数
i
i
i:
- 如果 n % i = 0 n \% i = 0 n%i=0,说明 i i i 是 n n n 的因数,将 c n t cnt cnt 加 1。
- 如果 i × i ≠ n i \times i \neq n i×i=n,说明 n i \frac{n}{i} in 也是 n n n 的因数,将 c n t cnt cnt 再加 1。
- 输出 c n t cnt cnt 的值。
时间复杂度分析:
该算法只需要枚举从 1 到
n
\sqrt{n}
n 的所有数,时间复杂度为
O
(
n
)
O(\sqrt{n})
O(n)。
空间复杂度分析:
该算法只需要常数级别的额外空间,空间复杂度为
O
(
1
)
O(1)
O(1)。
代码实现:
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n;
cin >> n;
int cnt = 0;
for (int i = 1; i <= sqrt(n); i++) {
if (n % i == 0) {
cnt++;
if (i * i != n) {
cnt++;
}
}
}
cout << cnt << endl;
return 0;
}
该代码先读入正整数 n n n,然后从 1 枚举到 n \sqrt{n} n,对于每个数 i i i,判断 n % i n \% i n%i 是否为 0。如果为 0,说明 i i i 是 n n n 的因数,将计数器 c n t cnt cnt 加 1。如果 i × i ≠ n i \times i \neq n i×i=n,说明 n i \frac{n}{i} in 也是 n n n 的因数,将 c n t cnt cnt 再加 1。最后输出 c n t cnt cnt 的值,即 n n n 的正因数的个数。
与解法 1 相比,解法 2 的时间复杂度从 O ( n ) O(n) O(n) 降低到了 O ( n ) O(\sqrt{n}) O(n),在 n n n 较大时可以显著提高算法效率。
1.3.3 天秀的完美数
天秀在学习完数的概念。一个正整数如果等于除它本身外的所有因数之和,就称为完美数。例如,6 就是一个完美数,因为 6 = 1 + 2 + 3。
现在,天秀想知道在给定的区间 [ l , r ] [l, r] [l,r] 内,有多少个完美数。
请你帮助天秀计算完美数的个数。
输入格式
输入两个正整数 l l l 和 r r r,表示查询的区间。
输出格式
输出一个整数,表示区间 [ l , r ] [l, r] [l,r] 内完美数的个数。
样例输入1
1 10
样例输出1
1
样例解释1
在区间 [ 1 , 10 ] [1, 10] [1,10] 内,只有 6 是完美数。
样例输入2
1 100
样例输出2
2
样例解释2
在区间 [ 1 , 100 ] [1, 100] [1,100] 内,有两个完美数:6 和 28。
数据范围
对于所有评测用例, 1 ≤ l ≤ r ≤ 1 0 6 1 \leq l \leq r \leq 10^6 1≤l≤r≤106。
题目分析:
要判断一个数是否为完美数,我们需要找到这个数的所有因数(不包括它本身),并计算这些因数的和。如果因数和等于这个数,那么它就是完美数。
一个简单的算法是:对于区间 [ l , r ] [l, r] [l,r] 内的每个数 i i i,枚举从 1 到 i − 1 i-1 i−1 的所有数,判断每个数是否为 i i i 的因数,并计算因数和。如果因数和等于 i i i,那么将完美数计数器加 1。
优化:
我们可以利用因数的对称性来优化算法。对于一个数
i
i
i 的任意因数
j
j
j,如果
j
×
k
=
i
j \times k = i
j×k=i,那么
k
k
k 也一定是
i
i
i 的因数。因此,我们只需要枚举从 1 到
i
\sqrt{i}
i 的所有数,对于每个因数
j
j
j,判断
i
j
\frac{i}{j}
ji 是否也是
i
i
i 的因数。这样可以避免重复计算,提高算法效率。
具体算法如下:
- 初始化完美数计数器 c n t = 0 cnt = 0 cnt=0。
- 从
l
l
l 枚举到
r
r
r,对于每个数
i
i
i:
- 初始化因数和 s u m = 1 sum = 1 sum=1。
- 从 2 枚举到
i
\sqrt{i}
i,对于每个数
j
j
j:
- 如果 i % j = 0 i \% j = 0 i%j=0,说明 j j j 是 i i i 的因数,将 s u m sum sum 加上 j j j。
- 如果 j × j ≠ i j \times j \neq i j×j=i,说明 i j \frac{i}{j} ji 也是 i i i 的因数,将 s u m sum sum 加上 i j \frac{i}{j} ji。
- 如果 s u m = i sum = i sum=i,说明 i i i 是完美数,将 c n t cnt cnt 加 1。
- 输出 c n t cnt cnt 的值。
时间复杂度分析:
该算法需要枚举从
l
l
l 到
r
r
r 的所有数,对于每个数,需要枚举从 1 到
i
\sqrt{i}
i 的所有数。因此,时间复杂度为
O
(
r
×
r
)
O(r \times \sqrt{r})
O(r×r)。
空间复杂度分析:
该算法只需要常数级别的额外空间,空间复杂度为
O
(
1
)
O(1)
O(1)。
代码实现:
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int l, r;
cin >> l >> r;
int cnt = 0;
for (int i = l; i <= r; i++) {
int sum = 1;
for (int j = 2; j <= sqrt(i); j++) {
if (i % j == 0) {
sum += j;
if (j * j != i) {
sum += i / j;
}
}
}
if (sum == i) {
cnt++;
}
}
cout << cnt << endl;
return 0;
}
该代码先读入区间范围 l l l 和 r r r,然后从 l l l 枚举到 r r r,对于每个数 i i i,判断它是否为完美数。具体做法是,初始化因数和 s u m sum sum 为 1,然后从 2 枚举到 i \sqrt{i} i,对于每个数 j j j,如果 i % j = 0 i \% j = 0 i%j=0,说明 j j j 是 i i i 的因数,将 s u m sum sum 加上 j j j。如果 j × j ≠ i j \times j \neq i j×j=i,说明 i j \frac{i}{j} ji 也是 i i i 的因数,将 s u m sum sum 加上 i j \frac{i}{j} ji。最后,如果 s u m = i sum = i sum=i,说明 i i i 是完美数,将计数器 c n t cnt cnt 加 1。输出 c n t cnt cnt 的值即为区间 [ l , r ] [l, r] [l,r] 内完美数的个数。
2.最大公约数
2.1 最大公约数介绍(GCD)
最大公约数(Greatest Common Divisor, GCD)是数论中的一个重要概念。两个或多个整数的最大公约数是能够同时整除这些整数的最大正整数。
例如,12 和 18 的最大公约数是 6,因为 6 是最大的能同时整除 12 和 18 的正整数。
最大公约数有以下性质:
- g c d ( a , b ) = g c d ( b , a ) gcd(a, b) = gcd(b, a) gcd(a,b)=gcd(b,a)(交换律)
- g c d ( a , b ) = g c d ( − a , b ) = g c d ( a , − b ) = g c d ( − a , − b ) gcd(a, b) = gcd(-a, b) = gcd(a, -b) = gcd(-a, -b) gcd(a,b)=gcd(−a,b)=gcd(a,−b)=gcd(−a,−b)(与符号无关)
- 如果 a ∣ b a \mid b a∣b,则 g c d ( a , b ) = ∥ a ∥ gcd(a, b) = \|a\| gcd(a,b)=∥a∥(整除性质)
- g c d ( a , 0 ) = ∥ a ∥ gcd(a, 0) = \|a\| gcd(a,0)=∥a∥(零元性质)
- g c d ( a , 1 ) = 1 gcd(a, 1) = 1 gcd(a,1)=1(单位元性质)
- g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a, b) = gcd(b, a \bmod b) gcd(a,b)=gcd(b,amodb)(模运算性质)
其中, a ∣ b a \mid b a∣b 表示 a a a 整除 b b b,即 b = k a b = ka b=ka,其中 k k k 是整数。 a m o d b a \bmod b amodb 表示 a a a 除以 b b b 的余数。
最大公约数的计算方法有以下几种:
- 枚举法:枚举从 1 到 m i n ( a , b ) min(a, b) min(a,b) 的所有数,找出最大的同时整除 a a a 和 b b b 的数。
- 辗转相除法(欧几里得算法):利用 g c d ( a , b ) = g c d ( b , a m o d b ) gcd(a, b) = gcd(b, a \bmod b) gcd(a,b)=gcd(b,amodb) 的性质,不断替换 a a a 和 b b b,直到 b = 0 b = 0 b=0,此时 a a a 就是最大公约数。
- 更相减损法:利用 g c d ( a , b ) = g c d ( a − b , b ) gcd(a, b) = gcd(a-b, b) gcd(a,b)=gcd(a−b,b) 的性质,不断用较大数减较小数,直到两数相等,此时的数就是最大公约数。
- 质因数分解法:将两个数分解质因数,取公共质因数的最小幂次,再相乘得到最大公约数。
在实际应用中,辗转相除法是最常用的计算最大公约数的方法,因为它的时间复杂度为 O ( log ( m a x ( a , b ) ) ) O(\log(max(a, b))) O(log(max(a,b))),效率很高。
最大公约数在求解线性丢番图方程、分数化简、密码学等领域有广泛应用。例如,在求解 a x + b y = c ax + by = c ax+by=c 的整数解时,需要先计算 g c d ( a , b ) gcd(a, b) gcd(a,b),然后判断 c c c 是否能被 g c d ( a , b ) gcd(a, b) gcd(a,b) 整除。在分数化简时,需要将分子和分母同时除以它们的最大公约数。在RSA加密算法中,需要选择两个大质数 p p p 和 q q q,并计算 g c d ( e , ( p − 1 ) ( q − 1 ) ) gcd(e, (p-1)(q-1)) gcd(e,(p−1)(q−1)),其中 e e e 是加密指数。
总之,最大公约数是数论中的基础概念,掌握它的性质和计算方法对于解决许多数学问题和实际应用都有重要意义。
2.2 最大公约数枚举法
题目:给定两个正整数 a a a 和 b b b,请你计算它们的最大公约数。
输入格式:输入两个正整数 a a a 和 b b b,用空格分隔。
输出格式:输出一个整数,表示 a a a 和 b b b 的最大公约数。
数据范围: 1 ≤ a , b ≤ 1 0 9 1 \leq a, b \leq 10^9 1≤a,b≤109
示例输入:
12 18
示例输出:
6
解析:
枚举法求最大公约数的基本思想是,从 1 到
m
i
n
(
a
,
b
)
min(a, b)
min(a,b) 依次枚举所有可能的公约数,找出最大的一个。
具体步骤如下:
- 确定枚举的上界。由于公约数不可能大于两个数中较小的那个,所以我们只需要枚举到 m i n ( a , b ) min(a, b) min(a,b)。
- 从 m i n ( a , b ) min(a, b) min(a,b) 开始,依次递减枚举每个数 i i i。
- 对于每个 i i i,判断它是否同时整除 a a a 和 b b b,即 a m o d i = 0 a \bmod i = 0 amodi=0 且 b m o d i = 0 b \bmod i = 0 bmodi=0。
- 如果 i i i 同时整除 a a a 和 b b b,则 i i i 就是一个公约数。由于我们是从大到小枚举的,因此第一个满足条件的 i i i 就是最大公约数,可以直接返回。
用C++代码实现如下:
#include <iostream>
#include <algorithm>
using namespace std;
int gcd(int a, int b) {
for (int i = min(a, b); i >= 1; i--) {
if (a % i == 0 && b % i == 0) {
return i;
}
}
return 1;
}
int main() {
int a, b;
cin >> a >> b;
cout << gcd(a, b) << endl;
return 0;
}
枚举法的时间复杂度为 O ( m i n ( a , b ) ) O(min(a, b)) O(min(a,b)),在最坏情况下需要枚举 m i n ( a , b ) min(a, b) min(a,b) 个数。当 a a a 和 b b b 很大时,这个方法会很慢。
但是,枚举法的思想很简单,容易理解和实现。在 a a a 和 b b b 比较小的情况下,枚举法是一个可行的选择。
同时,枚举法也是其他更高效算法的基础。例如,我们可以先用枚举法求出一个数的所有因数,然后再用这些因数去试除另一个数,判断它们是否为公因数。这样可以避免枚举所有 1 ∼ m i n ( a , b ) 1 \sim min(a, b) 1∼min(a,b) 的数,提高效率。
总之,虽然枚举法的效率不高,但它是最基本、最直观的求最大公约数的方法,掌握它的思想对于学习其他算法很有帮助。
2.3 最大公约数辗转相除法
题目:给定两个正整数 a a a 和 b b b,请你计算它们的最大公约数。
输入格式:输入两个正整数 a a a 和 b b b,用空格分隔。
输出格式:输出一个整数,表示 a a a 和 b b b 的最大公约数。
数据范围: 1 ≤ a , b ≤ 1 0 9 1 \leq a, b \leq 10^9 1≤a,b≤109
示例输入:
12 18
示例输出:
6
解析:
辗转相除法,也称欧几里得算法(Euclidean algorithm),是一种非常高效的求最大公约数的方法。它基于以下原理:
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
m
o
d
b
)
gcd(a, b) = gcd(b, a \bmod b)
gcd(a,b)=gcd(b,amodb)
其中, a m o d b a \bmod b amodb 表示 a a a 除以 b b b 的余数。
这个原理可以这样理解:如果 d d d 是 a a a 和 b b b 的公约数,那么 d d d 也一定是 b b b 和 a m o d b a \bmod b amodb 的公约数。反之,如果 d d d 是 b b b 和 a m o d b a \bmod b amodb 的公约数,那么 d d d 也一定是 a a a 和 b b b 的公约数。因此, a a a 和 b b b 的最大公约数等于 b b b 和 a m o d b a \bmod b amodb 的最大公约数。
基于这个原理,我们可以设计出如下算法:
- 如果 b = 0 b = 0 b=0,则返回 a a a 作为最大公约数。
- 否则,将 b b b 赋值给 a a a,将 a m o d b a \bmod b amodb 赋值给 b b b,然后返回步骤 1。
用C++代码实现如下:
#include <iostream>
using namespace std;
int gcd(int a, int b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
int main() {
int a, b;
cin >> a >> b;
cout << gcd(a, b) << endl;
return 0;
}
这个算法的时间复杂度为 O ( log ( m a x ( a , b ) ) ) O(\log(max(a, b))) O(log(max(a,b))),空间复杂度为 O ( 1 ) O(1) O(1)。它比枚举法快很多,特别是在 a a a 和 b b b 很大的情况下。
辗转相除法之所以高效,是因为它避免了不必要的枚举。通过不断用较大数除以较小数,直到余数为零,可以快速地缩小问题的规模,最终得到最大公约数。
同时,辗转相除法也有一个非递归的实现方式,使用循环来替代递归:
int gcd(int a, int b) {
while (b != 0) {
int r = a % b;
a = b;
b = r;
}
return a;
}
这种实现方式可以避免递归调用的开销,在某些情况下可能更快。
总之,辗转相除法是求最大公约数的标准算法,它高效、简洁,适用于各种情况。熟练掌握这个算法,对于解决数论问题非常有帮助。
2.4 最大公约数内置函数法
题目:给定两个正整数 a a a 和 b b b,请你计算它们的最大公约数。
输入格式:输入两个正整数 a a a 和 b b b,用空格分隔。
输出格式:输出一个整数,表示 a a a 和 b b b 的最大公约数。
数据范围: 1 ≤ a , b ≤ 1 0 9 1 \leq a, b \leq 10^9 1≤a,b≤109
示例输入:
12 18
示例输出:
6
解析:
C++提供了一个内置函数__gcd
,可以直接计算两个数的最大公约数。这个函数在algorithm
头文件中定义。
使用__gcd
函数的方法非常简单,只需要将两个数作为参数传入即可。函数会返回这两个数的最大公约数。
用C++代码实现如下:
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int a, b;
cin >> a >> b;
cout << __gcd(a, b) << endl;
return 0;
}
__gcd
函数内部实现的算法也是辗转相除法。所以它的时间复杂度和辗转相除法是一样的,都是
O
(
log
(
m
a
x
(
a
,
b
)
)
)
O(\log(max(a, b)))
O(log(max(a,b)))。
使用内置函数的优点是代码简洁,易于理解和维护。我们不需要自己实现算法的细节,只需要调用函数即可。这样可以减少出错的可能性,提高代码的可靠性。
但是,使用内置函数也有一些局限性。首先,不是所有的编程语言都提供了计算最大公约数的内置函数。其次,如果我们需要对算法进行一些特殊的优化或改进,使用内置函数就无法实现了。
尽管如此,在大多数情况下,使用内置函数是一个很好的选择。它结合了辗转相除法的高效和代码的简洁,是求最大公约数的一种常用方法。
当然,在实际问题中,我们需要根据具体的需求和条件,选择最适合的方法。有时候,我们可能需要自己实现算法,以满足特定的要求。但在一般情况下,内置函数可以帮我们节省很多时间和精力。
总之,__gcd
函数是C++提供的一个方便、高效的工具,用于计算最大公约数。掌握这个函数的用法,可以让我们在解决问题时更加得心应手。
3 最小公倍数
3.1 最小公倍数的介绍(LCM)
最小公倍数(Least Common Multiple, LCM)是另一个重要的数论概念。两个或多个整数的最小公倍数是能够同时被这些整数整除的最小正整数。
例如,12 和 18 的最小公倍数是 36,因为 36 是最小的能同时被 12 和 18 整除的正整数。
最小公倍数有以下性质:
- l c m ( a , b ) = l c m ( b , a ) lcm(a, b) = lcm(b, a) lcm(a,b)=lcm(b,a)(交换律)
- l c m ( a , b ) = l c m ( ∥ a ∥ , ∥ b ∥ ) lcm(a, b) = lcm(\|a\|, \|b\|) lcm(a,b)=lcm(∥a∥,∥b∥)(与符号无关)
- 如果 a ∣ b a \mid b a∣b,则 l c m ( a , b ) = ∥ b ∥ lcm(a, b) = \|b\| lcm(a,b)=∥b∥(整除性质)
- l c m ( a , 1 ) = ∥ a ∥ lcm(a, 1) = \|a\| lcm(a,1)=∥a∥(单位元性质)
- l c m ( a , 0 ) = 0 lcm(a, 0) = 0 lcm(a,0)=0(零元性质)
- l c m ( a , b ) = a b g c d ( a , b ) lcm(a, b) = \frac{ab}{gcd(a, b)} lcm(a,b)=gcd(a,b)ab(最小公倍数与最大公约数的关系)
其中, a ∣ b a \mid b a∣b 表示 a a a 整除 b b b,即 b = k a b = ka b=ka,其中 k k k 是整数。 g c d ( a , b ) gcd(a, b) gcd(a,b) 表示 a a a 和 b b b 的最大公约数。
最小公倍数的计算方法有以下几种:
- 枚举法:从 m a x ( a , b ) max(a, b) max(a,b) 开始,依次枚举每个数,直到找到第一个能同时被 a a a 和 b b b 整除的数。
- 质因数分解法:将两个数分解质因数,取每个质因数的最大幂次,然后相乘得到最小公倍数。
- 利用最大公约数:利用公式 l c m ( a , b ) = a b g c d ( a , b ) lcm(a, b) = \frac{ab}{gcd(a, b)} lcm(a,b)=gcd(a,b)ab,先求出最大公约数,然后代入公式计算最小公倍数。
在实际应用中,利用最大公约数的方法是最常用的,因为我们通常已经有了高效的求最大公约数的算法(如辗转相除法),可以直接使用。
最小公倍数在解决一些数学问题时非常有用,例如:
- 求解线性丢番图方程 a x ≡ b ( m o d m ) ax \equiv b \pmod{m} ax≡b(modm),其中 g c d ( a , m ) = 1 gcd(a, m) = 1 gcd(a,m)=1。
- 求解同余方程组 x ≡ a i ( m o d m i ) x \equiv a_i \pmod{m_i} x≡ai(modmi),其中 g c d ( m i , m j ) = 1 gcd(m_i, m_j) = 1 gcd(mi,mj)=1, i ≠ j i \neq j i=j。
- 计算分数的通分:将多个分数通分到相同的分母,分母是所有分母的最小公倍数。
此外,最小公倍数在一些实际问题中也有应用,如确定多个周期性事件的重合周期,安排多个任务的最小起始时间等。
总之,最小公倍数是数论中另一个基础概念,与最大公约数密切相关。掌握它的性质和计算方法,对于解决许多数学问题都有帮助。
3.2 最小公倍数利用最大公约数法
题目:给定两个正整数 a a a 和 b b b,请你计算它们的最小公倍数。
输入格式:输入两个正整数 a a a 和 b b b,用空格分隔。
输出格式:输出一个整数,表示 a a a 和 b b b 的最小公倍数。
数据范围: 1 ≤ a , b ≤ 1 0 9 1 \leq a, b \leq 10^9 1≤a,b≤109
示例输入:
12 18
示例输出:
36
解析:
利用最大公约数求最小公倍数的方法基于以下公式:
l
c
m
(
a
,
b
)
=
∣
a
b
∣
g
c
d
(
a
,
b
)
lcm(a, b) = \frac{|ab|}{gcd(a, b)}
lcm(a,b)=gcd(a,b)∣ab∣
其中, l c m ( a , b ) lcm(a, b) lcm(a,b) 表示 a a a 和 b b b 的最小公倍数, g c d ( a , b ) gcd(a, b) gcd(a,b) 表示 a a a 和 b b b 的最大公约数。
这个公式可以这样理解: a a a 和 b b b 的最小公倍数等于它们的乘积除以它们的最大公约数。因为 a a a 和 b b b 的乘积包含了所有 a a a 和 b b b 的因数,而除以它们的最大公约数,就去掉了重复的因数,留下的就是最小公倍数。
基于这个公式,我们可以设计出如下算法:
- 用辗转相除法或其他方法计算 g c d ( a , b ) gcd(a, b) gcd(a,b)。
- 计算 l c m ( a , b ) = ∣ a b ∣ g c d ( a , b ) lcm(a, b) = \frac{|ab|}{gcd(a, b)} lcm(a,b)=gcd(a,b)∣ab∣。
用C++代码实现如下:
#include <iostream>
using namespace std;
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
int lcm(int a, int b) {
return a / gcd(a, b) * b;
}
int main() {
int a, b;
cin >> a >> b;
cout << lcm(a, b) << endl;
return 0;
}
这个算法的时间复杂度取决于计算最大公约数的效率。如果使用辗转相除法,时间复杂度为 O ( log ( m a x ( a , b ) ) ) O(\log(max(a, b))) O(log(max(a,b)))。乘法和除法的时间复杂度为 O ( 1 ) O(1) O(1),所以总的时间复杂度为 O ( log ( m a x ( a , b ) ) ) O(\log(max(a, b))) O(log(max(a,b)))。
利用最大公约数求最小公倍数的方法非常高效,特别是在数很大的情况下。因为辗转相除法的时间复杂度很低,所以计算最小公倍数的时间复杂度也很低。
同时,这个方法也很容易理解和实现。只需要先计算最大公约数,然后代入公式计算最小公倍数,整个过程非常简洁。
此外,这个方法还有一个优点,就是不需要对数进行质因数分解。质因数分解是一个相对耗时的操作,特别是当数很大时。利用最大公约数求最小公倍数可以避免这个问题。
当然,这个方法也有一个小缺点,就是需要进行乘法和除法操作。在某些情况下,乘法和除法可能会导致整数溢出。但是,我们可以通过一些技巧来避免这个问题,例如先将 a a a 除以 g c d ( a , b ) gcd(a, b) gcd(a,b),再乘以 b b b,这样可以减小中间结果的大小。
总之,利用最大公约数求最小公倍数是一种非常实用、高效的方法。它结合了辗转相除法的优点,同时避免了质因数分解的缺点。在实际问题中,这种方法通常是求最小公倍数的首选。