欧几里得算法
欧几里得算法(也称辗转相除法)是目前已知求最大公约数的最快通用算法,具有代码复杂度低、易理解、用途广等诸多优点,也是OI中不可或缺的的一种算法。(当然更相减损术也是)
若有两个数a和b,需要求a和b的最大公约数,怎么办?
枚举它们的因子? 小数据可以,大数据的话,这个O(n)的算法就图森破了。
分解质因数?在大数面前也是拿衣服的。
总不能不搞吧,上帝喊了一声“效率”,于是我们便有了O(log n)效率极高的欧几里得算法。
引理
欧几里得有个非常强的定理,即gcd(a,b)=gcd(b,a mod b),让我们来证明一下
约定:mod <=> 取余,gcd <=> 最大公约数,| <=> 整除
假设a、b的公约数为k,a = bx + y
则 k | a,k | b,a mod b=y
因为 k | b,所以 k |bx,又因为 k | a,所以 k | (a - bx),即 k | y
而a mod b = y,所以 k | a mod b
再假设b、a mod b的公约数为kk,同理得 kk | a,所以(a,b)和(b,a mod b)的公约数是相同的,所以它们的最大公约数也是相同的.
所以gcd(a,b)=gcd(b,a mod b)
时间复杂度(讨论情况为a>=b)
a mod b必然是小于a/2的,而上一次的b会变成下一次的a,上一次的a mod b会变成下一次的b,最坏情况也就是b在a/2附近,即a mod b在a/2附近。在最坏情况时每次的值也会减少一半,所以说时间复杂度是O(log n)的。(实际中往往会更低)
算法实现
有了以上的引理算法实现就非常简单了,递归和非递归形式都可以。任何数与0的最大公约数是它本身,所以一直操作直到b = 0时的a就是原a,b的最大公约数了。
伪代码:
while(b){
r=a%b;
a=b;
b=r;
}
//最终的a即为原a,b最大公约数
扩展欧几里得算法
扩展欧几里得算法的功能就更强大了,它可以用来求二元一次方程的通解,还可以用来求乘法逆元。
在此顺便简介一下乘法逆元:
若有 a*x ≡ 1 (mod m),则称 x 为a关于m的乘法逆元,等价式 a * x+m * y = 1 (y=a*x/m)
这就也是个二元一次方程了,ExGcd可搞。
引理
裴蜀定理:若ax+by = z,则 gcd(a,b)| z
再顺手证明一下裴蜀定理:
设k = gcd(a,b),则 k | a, k | b,根据整除的性质,有 k | (ax+by)
设 s为ax+by的最小正数值
再设 q = [a / s](a整除s的值);r = a mod s = a-q(ax+by) = a(1 - qx)+b(-qy);
由此可见r也为a,b的线性组合;(ax+by称为a,b的线性组合)
又因为s为a,b的线性组合的最小正数值,0<= r < s,所以r的值为0,即 a mod s = r =0;s | a;
同理可得 s | b,则 s | k;
又因为 k | (ax+by),s为ax+by的最小正数值,所以 k | s;
因为 s | k,k | s,所以s = k;
原命题得证。
如何求解(以下讨论a>b)
显然当 b=0,gcd(a,b)=a。此时 x=1,y=0;
当a>b>0 时
设 ax1+ by1= gcd(a,b);
bx2+ (a mod b)y2= gcd(b,a mod b);
根据欧几里德原理有 gcd(a,b) = gcd(b,a mod b);
则:ax1+ by1= bx2+ (a mod b)y2;
即:ax1+ by1= bx2+ (a - [a / b] * b)y2 = ay2+ bx2- [a / b] * by2;
(a mod b = a - [a / b]*b;[a / b]代表a整除b)
也就是ax1+ by1 = ay2 + b(x2- [a / b] *y2);
根据恒等定理得:x1=y2;y1=x2- [a / b] *y2;
这样我们就得到了求解 x1,y1 的方法:x1,y1 的值基于 x2,y2
由引理我们知道:ax+by = z,z为gcd(a,b)若干倍。
所以我们先求解ax+by = gcd(a,b),再将求出的解乘以 z/gcd(a,b)就好了。
算法实现
我们依旧递归实现,因为上一次的x,y与下一次的x,y的值有关,所以我们从x=1,y=0(此时b=0)的情况开始递归上来,套公式就好了。(a,b下去,x,y上来)
伪代码:
int ExGcd(int a,int b,int &x,int &y){ //x,y可设为全局
if(!b){
x=1;
y=0;
return a;
}
int r=ExGcd(b,a%b,x,y);
t=x;
x=y;
y=t-a/b*y;
return r;
}
本文转载自@leader_one