最大公约数

辗转相除法不断把“大问题”换成“除数和余数”的小问题,直到余数为 $0$,最后的非零数就是最大公约数。

一句话算法

辗转相除法不断把“大问题”换成“除数和余数”的小问题,直到余数为 00,最后的非零数就是最大公约数。

问题模型

给定两个整数 a,ba,b,要求它们的最大公约数:

gcd(a,b) \gcd(a,b)

最大公约数是同时整除 aabb 的最大正整数。例如:

gcd(44,12)=4 \gcd(44,12)=4

因为 4444 \mid 444124 \mid 12,并且没有比 44 更大的正整数能同时整除它们。

朴素做法可以从 min(a,b)\min(a,b) 开始向下枚举因子,但最坏需要 O(min(a,b))O(\min(a,b))。欧几里得算法利用余数不断缩小问题,复杂度接近对数级。

核心直觉

设:

a=qb+r a = q b + r

这里 rraa 除以 bb 的余数。

如果一个数 dd 能同时整除 aabb,那么它也一定能整除:

aqb=r a-qb=r

也就是说,aabb 的共同因子,不会因为把 aa 换成 rr 而改变。

所以:

gcd(a,b)=gcd(b,amodb) \gcd(a,b)=\gcd(b,a\bmod b)

这就是欧几里得算法的核心。

“记忆方式”

每一步都把 (a, b) 变成 (b, a % b)。左边丢掉,右边变余数。

算法步骤

  1. b=0b=0,答案就是 a|a|
  2. 否则计算余数 r=amodbr=a\bmod b
  3. 把问题从 gcd(a,b)\gcd(a,b) 变成 gcd(b,r)\gcd(b,r)
  4. 重复上述过程,直到第二个数变成 00

gcd(44,12)\gcd(44,12) 为例:

gcd(44,12)=gcd(12,8)=gcd(8,4)=gcd(4,0)=4 \begin{aligned} \gcd(44,12) &= \gcd(12,8) \\ &= \gcd(8,4) \\ &= \gcd(4,0) \\ &= 4 \end{aligned}

算法证明

核心不变量:每一步替换后,两个数的所有公因子集合不变。

设:

a=qb+r a = qb + r

其中 qq 是整数,r=amodbr=a\bmod b

  1. 从左到右

    dad \mid adbd \mid b,则:

    d(aqb) d \mid (a-qb)

    又因为 aqb=ra-qb=r,所以:

    dr d \mid r

    因此 dd 也是 b,rb,r 的公因子。

  2. 从右到左

    dbd \mid bdrd \mid r,则:

    d(qb+r) d \mid (qb+r)

    又因为 qb+r=aqb+r=a,所以:

    da d \mid a

    因此 dd 也是 a,ba,b 的公因子。

  3. 结论

    a,ba,b 的公因子集合和 b,rb,r 的公因子集合完全相同,所以最大公约数也相同:

    gcd(a,b)=gcd(b,amodb) \gcd(a,b)=\gcd(b,a\bmod b)

每次取余后第二个数严格变小,过程一定会结束。结束时 gcd(x,0)=x\gcd(x,0)=|x|,所以算法正确。

复杂度分析

  • 时间复杂度:O(logmin(a,b))O(\log \min(a,b))
  • 空间复杂度:迭代写法为 O(1)O(1)

若使用递归写法,递归栈空间为 O(logmin(a,b))O(\log \min(a,b))

代码实现

cpp
        
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <bits/stdc++.h> using namespace std; int gcd(int a, int b) { if (a < 0) a = -a; if (b < 0) b = -b; while (b != 0) { int r = a % b; a = b; b = r; } return a; } int main() { int a, b; cin >> a >> b; cout << gcd(a, b) << "\n"; return 0; }

测试用例

输入:

44 12

输出:

4

过程:

44 % 12 = 8
12 % 8 = 4
8 % 4 = 0

所以答案是 4

再看一个互质例子:

35 64

输出:

1

应用分类详解

最大公约数的本质是:找两个整数共同拥有的最大“整除单位”。凡是题目里出现“约分、整除、周期同步、比例化简、线性组合”等信号,都应该想到 gcd。

一、分数约分与比例化简

典型模式: 需要把两个整数的比例化成最简形式。

识别信号: 题面出现“最简分数”“斜率比较”“比例相同”“方向向量去重”。

核心建模:g=gcd(a,b)g=\gcd(|a|,|b|),把 (a,b)(a,b) 变成 (a/g,b/g)(a/g,b/g)

应用场景 经典题目 核心思路
分数约分 luogu-P1888 分子分母同时除以 gcd
方向向量去重 LeetCode 149 斜率用约分后的 (dx, dy) 表示

二、整除结构与数论基础

典型模式: 问两个数是否有公共因子,或需要用互质性质推出结论。

识别信号: 题面出现“互质”“约数”“倍数”“整除”“最大公因数”。

核心建模:gcd(a,b)=1\gcd(a,b)=1,则 a,ba,b 没有公共质因子,可以使用互质相关性质。

应用场景 经典题目 核心思路
判断互质 luogu-P2152 先求 gcd,再根据是否为 11 判断
欧拉函数预备 luogu-P2158 统计互质关系时 gcd 是基础工具

三、最小公倍数与周期同步

典型模式: 两个周期什么时候再次同时发生。

识别信号: 题面出现“周期”“同时出现”“下一次相遇”“最小公倍数”。

核心建模: 先求 gcd,再用:

lcm(a,b)=agcd(a,b)×b \operatorname{lcm}(a,b)=\frac{a}{\gcd(a,b)}\times b
应用场景 经典题目 核心思路
周期同步 luogu-P1029 gcd 与 lcm 共同约束两个数
多周期合并 LeetCode 1201 用 gcd/lcm 处理容斥计数

四、扩展欧几里得的入口

典型模式: 要求解整数方程 ax+by=cax+by=c

识别信号: 题面出现“不定方程”“线性组合”“模逆元”“同余方程”。

核心建模: 方程 ax+by=cax+by=c 有整数解,当且仅当 gcd(a,b)c\gcd(a,b)\mid c。扩展欧几里得在 gcd 的递归结构上继续回代系数。

应用场景 经典题目 核心思路
线性同余方程 luogu-P1082 用 exgcd 求模逆元
不定方程 luogu-P5656 先判断 gcd 是否整除常数项

经典例题

1. luogu-P1888

给出三个数,按要求输出最简比例。核心操作是排序后取分子分母,再用 gcd 约分。

2. luogu-P1029

已知两个数的 gcd 和 lcm,反推满足条件的数对。利用 ab=gcd(a,b)×lcm(a,b)ab=\gcd(a,b)\times\operatorname{lcm}(a,b) 缩小搜索空间。

3. luogu-P1082

求乘法逆元,是扩展欧几里得的模板题。普通 gcd 给出递归骨架,exgcd 在这个骨架上回代出系数。

参考

  • 旧版文章:Rbook_ejs_old/book/math/numberTheory/gcd/index.md