求解最大公约数
问题:求两个数a , b a,b a , b 的最大公约数。
我们把a , b a,b a , b 的最大公约数记为g c d ( a , b ) gcd(a,b) g c d ( a , b ) 。
朴素算法
朴素的算法是,求出a , b a,b a , b 的素因子分解,取两个数共同的素因子,即为最大公约数:
a = p 1 a 1 p 2 a 2 . . . p n a n a=p_1^{a_1}p_2^{a_2}...p_n^{a_n}
a = p 1 a 1 p 2 a 2 ... p n a n
b = p 1 b 1 p 2 b 2 . . . p n b n b=p_1^{b_1}p_2^{b_2}...p_n^{b_n}
b = p 1 b 1 p 2 b 2 ... p n b n
g c d ( a , b ) = p 1 m i n ( a 1 , b 1 ) p 2 m i n ( a 2 , b 2 ) . . . p n m i n ( a n , b n ) gcd(a,b)=p_1^{min(a_1,b_1)}p_2^{min(a_2,b_2)}...p_n^{min(a_n,b_n)}
g c d ( a , b ) = p 1 min ( a 1 , b 1 ) p 2 min ( a 2 , b 2 ) ... p n min ( a n , b n )
a 1 , a 2 , . . . , a n , b 1 , b 2 , . . . , b n ≥ 0 a_1,a_2,...,a_n,b_1,b_2,...,b_n \ge 0 a 1 , a 2 , ... , a n , b 1 , b 2 , ... , b n ≥ 0 (任意数的0次幂等于1,一般我们会省略掉)
例如:588000 = 2 5 ∗ 3 ∗ 5 3 ∗ 7 2 588000=2^5*3*5^3*7^2 588000 = 2 5 ∗ 3 ∗ 5 3 ∗ 7 2 ,540 = 2 2 ∗ 3 3 ∗ 5 ∗ 7 0 540=2^2*3^3*5*7^0 540 = 2 2 ∗ 3 3 ∗ 5 ∗ 7 0
它们的最大公约数为:60 = 2 2 ∗ 3 ∗ 5 ∗ 7 0 60=2^2*3*5*7^0 60 = 2 2 ∗ 3 ∗ 5 ∗ 7 0
这个算法需要先对a , b a,b a , b 做素因子分解,计算量较大,且实现较为复杂,实际中我们不会使用这种方式计算最大公约数。
辗转相除法
有一种更高效的算法可以求最大公约数,叫作辗转相除法(也叫做欧几里得算法)。
在介绍这个算法之前,需要引入一个定理:
( a , b ) 的公约数与 ( b , a % b ) 的公约数完全相同 ( a % b 表示 a m o d b ,即取余操作 ) (a,b)的公约数与(b,a\%b)的公约数完全相同(a\%b表示a\ mod\ b,即取余操作)
( a , b ) 的公约数与 ( b , a % b ) 的公约数完全相同 ( a % b 表示 a m o d b ,即取余操作 )
证明:
如果a < b a<b a < b ,则( b , a % b ) = ( b , a ) (b,a\%b)=(b,a) ( b , a % b ) = ( b , a ) ,只是相当于交换了两个数的位置,显然公约数相同
如果a = b a=b a = b ,则( b , a % b ) = ( b , 0 ) = ( a , 0 ) (b,a\%b)=(b,0)=(a,0) ( b , a % b ) = ( b , 0 ) = ( a , 0 ) ,由于任意数都是0 0 0 的约数,显然公约数也是相同的
如果a > b a>b a > b ,我们需要从两个方向证明
如果d d d 是( a , b ) (a,b) ( a , b ) 的公约数,则d d d 也是( b , a % b ) (b,a\%b) ( b , a % b ) 的公约数:a = k ∗ b + c ( 0 < c < b ) c = a % b 记 a = k a ∗ d , b = k b ∗ d c = a − k ∗ b = k a ∗ d − k ∗ k b ∗ d = ( k a − k ∗ k b ) ∗ d ( k a − k ∗ k b ) 为整数 → d 为 c = a % b 的约数 → d 为 ( b , a % b ) 的公约数 \begin{align}
a=k*b+c(0<c<b) \\
c=a\%b \\
记a=k_a*d,b=k_b*d \\
c=a-k*b=k_a*d-k*k_b*d=(k_a-k*k_b)*d \\
(k_a-k*k_b)为整数 \\
\rightarrow d为c=a\%b的约数 \\
\rightarrow d为(b,a\%b)的公约数
\end{align}
a = k ∗ b + c ( 0 < c < b ) c = a % b 记 a = k a ∗ d , b = k b ∗ d c = a − k ∗ b = k a ∗ d − k ∗ k b ∗ d = ( k a − k ∗ k b ) ∗ d ( k a − k ∗ k b ) 为整数 → d 为 c = a % b 的约数 → d 为 ( b , a % b ) 的公约数
如果d d d 是( b , a % b ) (b,a\%b) ( b , a % b ) 的公约数,则d d d 也是( a , b ) (a,b) ( a , b ) 的公约数:a = k ∗ b + c ( 0 < c < b ) c = a % b 记 b = k b ∗ d , c = k c ∗ d a = k ∗ b + c = k ∗ k b ∗ d + k c ∗ d = ( k ∗ k b + k c ) ∗ d ( k ∗ k b + k c ) 为整数 → d 为 a 的约数 → d 为 ( a , b ) 的公约数 \begin{align}
a=k*b+c(0<c<b) \\
c=a\%b \\
记b=k_b*d,c=k_c*d \\
a=k*b+c=k*k_b*d+k_c*d=(k*k_b+k_c)*d \\
(k*k_b+k_c)为整数 \\
\rightarrow d为a的约数 \\
\rightarrow d为(a,b)的公约数
\end{align}
a = k ∗ b + c ( 0 < c < b ) c = a % b 记 b = k b ∗ d , c = k c ∗ d a = k ∗ b + c = k ∗ k b ∗ d + k c ∗ d = ( k ∗ k b + k c ) ∗ d ( k ∗ k b + k c ) 为整数 → d 为 a 的约数 → d 为 ( a , b ) 的公约数
由于( a , b ) (a,b) ( a , b ) 的公约数与( b , a % b ) (b,a\%b) ( b , a % b ) 的公约数相同,因此它们的最大公约数也是相同的
我们可以利用上述的定理得到一个递推算法,算法步骤如下:
g = g c d ( a , b ) = g c d ( b , a % b ) b ⇒ a 2 , a % b ⇒ b 2 g = g c d ( a 2 , b 2 ) = g c d ( b 2 , a 2 % b 2 ) . . . b i − 1 ⇒ a i , a i − 1 % b i − 1 ⇒ b i g = g c d ( a i , b i ) \begin{align}
g=gcd(a,b)=gcd(b,a\%b) \\
b\rArr a_2,a\%b\rArr b_2 \\
g=gcd(a_2,b_2)=gcd(b_2,a_2\%b_2) \\
... \\
b_{i-1}\rArr a_i,a_{i-1}\%b_{i-1}\rArr b_i \\
g=gcd(a_i,b_i)
\end{align}
g = g c d ( a , b ) = g c d ( b , a % b ) b ⇒ a 2 , a % b ⇒ b 2 g = g c d ( a 2 , b 2 ) = g c d ( b 2 , a 2 % b 2 ) ... b i − 1 ⇒ a i , a i − 1 % b i − 1 ⇒ b i g = g c d ( a i , b i )
由于a i , b i a_i,b_i a i , b i 在不断减小,所以递推式必定会在b i = 0 b_i=0 b i = 0 时结束,此时a i a_i a i 即为a , b a,b a , b 的最大公约数,不存在比a a a 还要大的约数了。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 long long gcd (long long a, long long b) { if (b==0 ) return a; return gcd (b, a%b); } long long gcd (long long a, long long b) { return b ? gcd (b, a % b) : a; }long long gcd (long long a, long long b) { while (b != 0 ) { long long pa = a; a = b; b = pa % b; } return a; }
扩展欧几里得算法
如果g g g 为( a , b ) (a,b) ( a , b ) 的最大公约数,则必定存在一对整数x , y x,y x , y ,使得g = a ∗ x + b ∗ y g=a*x+b*y g = a ∗ x + b ∗ y ,我们可以用扩展欧几里得算法来计算x , y x,y x , y 的值
方法步骤如下:在使用辗转相除法计算( a , b ) (a,b) ( a , b ) 的最大公约数时,假设最终迭代了n n n 次,最终得到( a n , b n = 0 ) (a_n,b_n=0) ( a n , b n = 0 ) 的最大公约数为g = a n g=a_n g = a n ,我们可以得到( x n = 1 , y n = 0 ) (x_n=1,y_n=0) ( x n = 1 , y n = 0 ) :
g = a n ∗ 1 + b n ∗ 0 g=a_n*1+b_n*0
g = a n ∗ 1 + b n ∗ 0
根据数学归纳法,当我们知道满足以下等式的( x i , y i ) (x_i,y_i) ( x i , y i ) 的值时
g = a i ∗ x i + b i ∗ y i g=a_i*x_i+b_i*y_i
g = a i ∗ x i + b i ∗ y i
我们可以通过辗转相除法的递推关系倒推得到( x i − 1 , y i − 1 ) (x_{i-1},y_{i-1}) ( x i − 1 , y i − 1 ) 的值,具体做法如下:
a i = b i − 1 a_i=b_{i-1}
a i = b i − 1
b i = a i − 1 % b i − 1 = a i − 1 − ⌊ a i − 1 b i − 1 ⌋ ∗ b i − 1 b_i=a_{i-1}\%b_{i-1}=a_{i-1}- \lfloor \frac{a_{i-1}}{b_{i-1}} \rfloor*b_{i-1}
b i = a i − 1 % b i − 1 = a i − 1 − ⌊ b i − 1 a i − 1 ⌋ ∗ b i − 1
⇒ g = a i ∗ x i + b i ∗ y i = b i − 1 ∗ x i + ( a i − 1 − ⌊ a i − 1 b i − 1 ⌋ ∗ b i − 1 ) ∗ y i = a i − 1 ∗ y i + b i − 1 ∗ ( x i − ⌊ a i − 1 b i − 1 ⌋ ∗ y i ) \rArr g=a_i*x_i+b_i*y_i=b_{i-1}*x_i+(a_{i-1}- \lfloor \frac{a_{i-1}}{b_{i-1}} \rfloor*b_{i-1})*y_i\\=a_{i-1}*y_i+b_{i-1}*(x_i-\lfloor \frac{a_{i-1}}{b_{i-1}} \rfloor*y_i)
⇒ g = a i ∗ x i + b i ∗ y i = b i − 1 ∗ x i + ( a i − 1 − ⌊ b i − 1 a i − 1 ⌋ ∗ b i − 1 ) ∗ y i = a i − 1 ∗ y i + b i − 1 ∗ ( x i − ⌊ b i − 1 a i − 1 ⌋ ∗ y i )
⇒ x i − 1 = y i , y i − 1 = x i − ⌊ a i − 1 b i − 1 ⌋ ∗ y i \rArr x_{i-1}=y_i,y_{i-1}=x_i-\lfloor \frac{a_{i-1}}{b_{i-1}} \rfloor*y_i
⇒ x i − 1 = y i , y i − 1 = x i − ⌊ b i − 1 a i − 1 ⌋ ∗ y i
由此递推式,如果我们已知x n , y n x_n,y_n x n , y n ,便可以计算得到x n − 1 , y n − 1 x_{n-1},y_{n-1} x n − 1 , y n − 1 ,不断重复此过程,最终可以得到一对x , y x,y x , y ,使得g = a ∗ x + b ∗ y g=a*x+b*y g = a ∗ x + b ∗ y
代码实现
1 2 3 4 5 6 7 8 9 10 long long exgcd (long long a, long long b, long long & x, long long & y) { if (b==0 ) { x = 1 ; y = 0 ; return a; } long long g = exgcd (b, a%b, y, x); y = y - a/b * x; return g; }
迭代版本
上述的递推式需要先知道x i , y i x_i,y_i x i , y i ,才能够计算x i − 1 , y i − 1 x_{i-1},y_{i-1} x i − 1 , y i − 1 ,看起来无法使用非递归方式迭代实现,然而,我们通过观察可以得到:
{ x i − 1 = y i y i − 1 = x i − ⌊ a i − 1 b i − 1 ⌋ ∗ y i \left \{
\begin{aligned}
x_{i-1} & = y_i \\
y_{i-1} & = x_i-\lfloor \frac{a_{i-1}}{b_{i-1}} \rfloor*y_i
\end{aligned}
\right.
⎩ ⎨ ⎧ x i − 1 y i − 1 = y i = x i − ⌊ b i − 1 a i − 1 ⌋ ∗ y i
这可以写成矩阵的形式:
[ x i − 1 y i − 1 ] = [ 0 1 1 − ⌊ a i − 1 b i − 1 ⌋ ] ∗ [ x i y i ] \begin{bmatrix}
x_{i-1} \\
y_{i-1}
\end{bmatrix}
=
\begin{bmatrix}
0 & 1 \\
1 & -\lfloor \frac{a_{i-1}}{b_{i-1}} \rfloor
\end{bmatrix}
*
\begin{bmatrix}
x_{i} \\
y_{i}
\end{bmatrix}
[ x i − 1 y i − 1 ] = [ 0 1 1 − ⌊ b i − 1 a i − 1 ⌋ ] ∗ [ x i y i ]
将迭代过程中的所有项展开后:
[ x y ] = [ 0 1 1 − ⌊ a b ⌋ ] ∗ [ 0 1 1 − ⌊ a 1 b 1 ⌋ ] ∗ . . . ∗ [ 0 1 1 − ⌊ a n − 1 b n − 1 ⌋ ] ∗ [ x n = 1 y n = 0 ] \begin{bmatrix}
x \\
y
\end{bmatrix}
=
\begin{bmatrix}
0 & 1 \\
1 & -\lfloor \frac{a}{b} \rfloor
\end{bmatrix}
*
\begin{bmatrix}
0 & 1 \\
1 & -\lfloor \frac{a_{1}}{b_{1}} \rfloor
\end{bmatrix}
*...*
\begin{bmatrix}
0 & 1 \\
1 & -\lfloor \frac{a_{n-1}}{b_{n-1}} \rfloor
\end{bmatrix}
*
\begin{bmatrix}
x_{n} = 1 \\
y_{n} = 0
\end{bmatrix}
[ x y ] = [ 0 1 1 − ⌊ b a ⌋ ] ∗ [ 0 1 1 − ⌊ b 1 a 1 ⌋ ] ∗ ... ∗ [ 0 1 1 − ⌊ b n − 1 a n − 1 ⌋ ] ∗ [ x n = 1 y n = 0 ]
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 long long exgcd (long long a, long long b, long long & x, long long & y) { long long w[2 ][2 ]; w[0 ][1 ] = w[1 ][0 ] = 0 ; w[0 ][0 ] = w[1 ][1 ] = 1 ; while (b != 0 ) { x = w[0 ][0 ]; w[0 ][0 ] = w[0 ][1 ]; w[0 ][1 ] = x - a/b * w[0 ][1 ]; y = w[1 ][0 ]; w[1 ][0 ] = w[1 ][1 ]; w[1 ][1 ] = y - a/b * w[1 ][1 ]; long long pa = a; a = b; b = pa % b; } x = w[0 ][0 ]; y = w[1 ][0 ]; return a; }