拓展欧几里得

时间:2021-12-08 05:17:46

欧几里得算法

 

  欧几里得算法(也称辗转相除法)是目前已知求最大公约数的最快通用算法,具有代码复杂度低、易理解、用途广等诸多优点,也是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