题目
从标准输入读入两个正整数 a 和 b,计算并输出它们的最小公倍数(LCM)。
输入格式:一行输入两个正整数 a 和 b,以空格分隔。数据范围:1 ≤ a, b ≤ 109。
输出格式:输出一个整数,表示 a 和 b 的最小公倍数,末尾输出换行。
思路与算法
1. 数学原理
两个正整数 a 和 b 的最大公约数记为 gcd(a, b),最小公倍数记为 lcm(a, b)。由数论性质可知,两数之积等于它们最大公约数与最小公倍数的乘积,即:a × b = gcd(a, b) × lcm(a, b)
由此推导最小公倍数的计算公式为:lcm(a, b) = (a × b) / gcd(a, b)
2. 核心考点:运算顺序与防溢出
如果代码中直接计算 (a * b) / gcd,当两个数都在 105 左右时,乘积 a * b 会达到 1010,超出 32 位有符号整型 int 的上限(约 2.14×109),导致溢出错误。
因为 gcd 是 a 的公因数,a / gcd 一定能整除。所以应当先除后乘:(long long)a / gcd * b
先除法后乘法既不会损失精度,又能有效控制计算的中间数值,避免溢出。同时使用 long long 保存计算结果。
3. 辗转相除法求 GCD
求解最大公约数使用欧几里得算法(辗转相除法):gcd(a, b) = gcd(b, a % b),重复取余直到余数为 0,此时的除数就是最大公约数。单次计算的时间复杂度为 O(log(min(a, b)))。
完整程序
#include <stdio.h>
/**
* 辗转相除法求两数最大公约数(欧几里得算法)
*/
static long long gcd(long long a, long long b)
{
while (b != 0) {
long long t = a % b;
a = b;
b = t;
}
return a;
}
int main(void)
{
long long a, b;
// 读入两数并校验是否为正整数
if (scanf("%lld %lld", &a, &b) != 2 || a <= 0 || b <= 0) {
return 1;
}
// 先求出最大公约数
long long g = gcd(a, b);
// 关键技巧:先除以 g 再乘 b,防止 a * b 中间结果溢出
long long lcm = (a / g) * b;
// 输出最小公倍数
printf("%lld\n", lcm);
return 0;
}
运行示例
示例 1(常规数值):
输入:
4 6输出:
12示例 2(较大数值):
输入:
150000 250000输出:
750000其它写法
下面每种写法都是完整程序,输入输出格式与正文一致,便于对照。
写法一:递归求最大公约数
欧几里得算法写成递归形式代码更短,递归深度通常在几十次以内,调用栈开销极小。
#include <stdio.h>
// 递归求最大公约数:b 为 0 时递归终止
static long long gcd(long long a, long long b)
{
return b == 0 ? a : gcd(b, a % b);
}
int main(void)
{
long long a, b;
if (scanf("%lld %lld", &a, &b) != 2 || a <= 0 || b <= 0) {
return 1;
}
// 先除后乘,避免溢出
long long lcm = (a / gcd(a, b)) * b;
printf("%lld\n", lcm);
return 0;
}
写法二:定义法枚举(仅适合极小数值)
从较大数 max(a, b) 开始,每次累加一个该数值的步长,直到找到第一个能同时整除 a 和 b 的数。
说明:该方法的时间复杂度取决于最小公倍数的大小,数值较大或两数互质时循环次数极大,容易超时,仅用于理解最小公倍数的数学定义。
#include <stdio.h>
int main(void)
{
long long a, b;
if (scanf("%lld %lld", &a, &b) != 2 || a <= 0 || b <= 0) {
return 1;
}
// 以两数中的较大值为步长累加
long long step = (a > b) ? a : b;
long long m = step;
while (m % a != 0 || m % b != 0) {
m += step;
}
printf("%lld\n", m);
return 0;
}