浅谈最大公约数
dangerous_tDp · · 算法·理论
写在前面
为备忘记录下,若要转载请标明出处(话说这么烂也没人会转载吧),感谢管理审核。
最大公约数
最大公约数(Greatest Common Divisor),简称 gcd。任意一组整数
性质
- 非负性,即对于任意整数
a_1, a_2, a_3, ..., a_{n - 1}, a_n ,有\gcd(a_1, a_2, ..., a_n) = \gcd(|a_1|, |a_2|, ..., |a_n|) \ge 0 (由定义易证)。 - 轮换对称性(“交换性”似乎不太严谨),即
\gcd(a, b) = \gcd(b, a) (可以拓展到更多个数的情况,易证)。 - 结合性,即
\gcd(a, b, c) = \gcd(\gcd(a, b), c) (同样可以拓展到更多情况,易证)。 -
- 对于一组整数
a_1, a_2, ..., a_n ,令d = \gcd(a_1, a_2, ..., a_n) ,则有\gcd(\frac{a_1}{d}, \frac{a_2}{d}, ..., \frac{a_n}{d}) = 1 ,由定义易证。 - 随便根据定义推一下就有一大堆,再此不多赘述......
求法
这是本篇文章主要讨论的部分,下面会分别讲解一些常用的最大公约数求法。
欧几里得算法
欧几里得算法(Euclidean algorithm),又名辗转相除法,常用于求两数
算法原理
证明
令
令
又因为
故
所以,我们可以递归地求得
时间复杂度分析
若
若
-
若
a < 2 \times b ,不妨令a = 2 \times x ,b = x + y \ (0 < y < x) 。
则第一次递归后有\gcd(b, a \bmod b) = \gcd(b, a - b) = \gcd(x + y, x - y) 。
那么第二次递归后有\gcd(x - y, (x + y) \bmod (x - y)) ,令r = (x + y) \bmod (x - y) ,由取模运算的定义得r < x - y 。
又有x - y < x 和x = \frac{a}{2} ,所以r < x - y < \frac{a}{2} 。那么可得每两次递归,数据至多变为原来的\frac{1}{2} ,则时间复杂度为O(2 \times \log \max(a, b)) \approx O(\log \max(a, b)) 。 -
若
a > 2 \times b ,不妨令a = 2 \times x ,b = x - y \ (0 < y < x) 。
则第一次递归后有\gcd(b, a \bmod b) = \gcd(x - y, 2 \times x \bmod (x - y)) ,令r = 2 \times x \bmod (x - y) ,由取模运算的定义得r < x - y 。
又有x - y < x 和x = \frac{a}{2} ,所以r < x - y < \frac{a}{2} 。即每次递归,数据至多会变为原来的\frac{1}{2} ,则时间复杂度为O(\log \max(a, b)) 。 -
若
a = 2 \times b ,则一次递归后变为\gcd(b, 0) ,时间复杂度为O(1) 。
若
所以,欧几里得算法的平均时间复杂度为
Code
#include <bits/stdc++.h>
using namespace std;
int x, y;
int gcd(int a, int b){
if (!b)return a;//若当前 b 等于 0,结束递归
return gcd(b, a % b);
}
int main(){
cin >> x >> y;
cout << gcd(x, y);
return 0;
}
更相减损术(Stein 优化)
欧几里得算法足以满足大部分的求解需求,但是取模运算的耗时较长,若在大整数运算中变会捉襟见肘。所以,一种新的解法应运而生,这便是更相减损术(Stein 优化)。
其运算过程为先判断参与运算的二者
算法原理
-
\gcd(a, b) = \gcd(b, a - b) \ (a \ge b) -
\gcd(a, b) = \gcd(\frac{a}{2}, b) \ (2 \mid a \text{ 且 } 2 \nmid b)
证明
原理 1:
令
\gcd(a, b) = d ,则有a = d \times x ,b = d \times y ,x \nmid y 。所以
a - b = d \times (x - y) ,即a 和b 的所有公因数都是b 和a - b 的公因数。继续证明
y \nmid x - y ,令b 和a - b 的最大公因数为h ,则有h \ge d 和d \mid h ,令g = \frac{h}{d} ,则有b = n \times h = y \times g \times d 和a - b = m \times h = (x - y) \times g \times d 。所以有
a = a - b + b = x \times g \times d = x \times d ,即g = 1 ,所以h = d ,可得x \nmid x - y ,得证。
原理 2(其实没什么证的):
因为已知
2 \mid a 和2 \nmid b ,所以2 \nmid \gcd(a, b) ,那么就可以将a 中的2 分离出来,即\gcd(a, b) = \gcd(\frac{a}{2}, b) ,得证。
时间复杂度分析
-
若
a、b 均为奇数,且a > b \ge 1 ,令w = \frac{a - b}{2} -
若
b \le \frac{a}{2} ,则a - b \le \frac{a}{2} ,w \le \frac{a}{4} 。那么可得\max(b, w) \le \max(\frac{a}{2}, \frac{a}{4}) = \frac{a}{2} ,此时\max(a, b) 数量级减半。 -
若
b > \frac{a}{2} ,结合a、b 为奇数得b \ge \frac{a + 1}{2} \implies a - b \le \frac{a - 1}{2} \implies w < \frac{a}{4} 。一次递归后可得\gcd(b, w) ,对其进行第二次迭代: -
-
-
若
a、b 中有一偶数,同上。 -
若
a、b 均为偶数,递归一次后得到\gcd(\frac{a}{2}, \frac{b}{2}) ,数量级减半。
综上,此算法的时间复杂度是是在
Code
#include <bits/stdc++.h>
using namespace std;
int x, y;
int gcd(int a, int b){
if (!b)return a;
if (b > a)swap(a, b);
if (!(a & 1) && !(b & 1))return gcd(a >> 1, b >> 1) << 1;//都为偶数
if ((a & 1) ^ (b & 1))return gcd(a & 1 ? a : a >> 1, b & 1 ? b : b >> 1);//一奇一偶
return gcd(b, a - b >> 1);
}
int main(){
cin >> x >> y;
cout << gcd(x, y);
return 0;
}
非递归版:
int gcd(int a, int b){
if (a == b)return a;
int az = ctz(a), az = ctz(b), z = az < bz ? az : bz, d;
a >>= az;
while (b)
b >>= bz, bz = ctz(d = b - a),
a = b < a ? b : a, b = abs(d);
return a << z;
}//其中 ctz 为 __builtin_ctz,即数二进制末尾 0 的个数
拓展欧几里得算法
拓展欧几里得算法(Extended Euclidean algorithm, EXGCD),常被用于求解
裴蜀定理
内容
若有整数
即对于方程
证明
令集合
则对于任意
那么
特别地,当
设
实现
那么如何求
我们知道最大公约数可以使用辗转相除法求得,所以我们一步步由递归函数往上推。先把代码放上来:
int gcd(int a, int b){
if (!b)return a;
return gcd(b, a % b);
}
首先当
那么
也即
化简得
所以当前层的
so the code:
void exgcd(int a, int b){
if (!b){
x = 1, y = 0;
return;
}
exgcd(b, a % b);
int now = x;
x = y, y = now - a / b * y;
}
求通解
我们把
由裴蜀定理得若
从
即
接下来求其通解,我们先让
由
把
由此得到通解。
求最小非负整数解
那如何求最小非负整数解呢?
因为
即
同理得
结语
乱写,有误还请指出,再次感谢审核。