欧几里得、扩展的欧几里得算法

 最大公约数(Greatest Common Divisor)

 

欧几里得算法:

定理1:设a,b,c,q都为整数,且b>0。如果 a = q b+c,那么 gcd(a, b) = gcd(b, c)

证明方法用了集合的方法,就是说明一个的约数必定是另一个数的约数,从而两个数相等。

 

用这个定理就可以写出欧几里得算法

(1)迭代版本

int ITERATIVE-GCD(int a, int b) { int r = a % b; while (r) { a = b; b = r; r = a % b; } return b; }

(2)递归版本

int RECURSIVE-GCD(int a, int b) { if (b == 0) return a; else return RECURSIVE-GCD(b, a % b); }

 

显然,递归的比迭代的写起来方便,不容易错,但是效率是否会差很多呢?

 

定理2:(由法国数学家拉梅证明)欧几里得算法所需除法次数不超过m和n中较小的那个数的十进制位数的5倍

定理3:(稍弱点)……不超过2 log(n+1),其中n为较小的数

 

有了上面的定理,有理由相信递归版本的优势,但学的时候两种都要会!

 

 

 

 

拓展的欧几里得算法(Extended Euclidean Algorithm)

可以用来求二元一次方程组的整数解问题

ax + by = m,要求gcd(a, b) = 1,否则两边同除以GCD,最后结果再乘以GCD

(3)迭代的拓展欧几里得算法

#include int extended_euclidean(int n, int m, int &x, int &y) { int x1 = 1, x2 = 0, x3 = n; int y1 = 0, y2 = 1, y3 = m; while (x3 % y3 != 0) { int d = n / m; int t1, t2, t3; t1 = x1 - d * y1; t2 = x2 - d * y2; t3 = x3 - d * y3; x1 = y1; x2 = y2; x3 = y3; y1 = t1; y2 = t2; y3 = t3; }; x = y1; y = y2; return y3; } int main() { int n, m; while (scanf("%d%d", &n, &m) == 2) { int x, y, gcd; gcd = extended_euclidean(n, m, x, y); printf("The GCD of %d and %d is %d ./n %d * %d + %d * %d = 1/n", n, m, gcd, n, x, m, y); } return 0; }

 

(4)递归的拓展欧几里得算法

int extended_euclidean(int n, int m, int &x, int &y) { if (m == 0) { x = 1; y = 0; return n; } int g = extended_euclidean(m, n % m, x, y); int t = x - n / m * y; x = y; y = t; return g; }

 

递归的容易写,容易记。

 

知道了一个解,该方程的所有整数解都可以表示出来

x = x0 + b k

y = y0 + a k

这样,如果要求最小的正整数解也就可以算出来了!

 

原理:摘自网上

令a1=a/gcd(a,b),b1=b/gcd(a,b),m1=m/gcd(a,b)。如果我们能够首先求出满足a*x1+b*y1=gcd(a,b)这个方程的x1和y1,那么x=x1*m1,y=y1*m1就可以求出来了。由欧几里德算法gcd(a,b)=gcd(b,a%b),所以a*x1+b*y1=gcd(a,b)=gcd(b,a%b)=b*x2+(a%b)*y2,现在只要做一些变形就可以得到扩展欧几里德算法中的用到的式子了。令k=a/b(商),r=a%b(余数),那么a=k*b+r。所以r=a-k*b,带入上式,得到a*x1+b*y1=b*x2+(a-(a/b)*b)y2=a*y2+b*(x2-(a/b)*y2) => x1=y2,y1=x2-(a/b)*y2。有了这两个式子我们就知道了在用欧几里德求最大公约数的时候,相应的参数x,y的变化。现在再回过头来看一下扩展欧几里德算法的代码就很好理解了,实际上扩展欧几里德就是在求a和b的最大公约数的同时,也将满足方程a*x1+b*y1=gcd(a,b)的一组x1和y1的值求了出来。下面代码中突出的部分就是标准的欧几里德算法的代码。

 

应用:

(1)求数对于某个质数的逆元

extended_euclidean(n, p, x, y); x = x % p; if (x < 0) x += p;//由于 x 有可能是负数,故还要对 p 取模

 

 (2)  USACO 4.1 nuggets 正整数解的存在问题

ax+by=c, where gcd(a,b)=1, a>0, b>0;  a posivesolution exists if c>=a*b
 PROOF:
from Extended Euclidean, we know that there exists a solution ax0+by0=c
**   we want to find xn, yn, where xn>0, yn>0. xn = x0 + b*t, yn = y0 - a*t;
**   -x0/b <= t <= y0/a。for c >= a*b, we know that x0/b+y0/a>=1, so it exists

 

(3)FZU APRIL 排列

/********************************* ** Description: C(N,M)%P (1 <= N <= 10E9, 1 <= M <= 10E4, 1 <= P <= 10E9) ** Algorithm: EXTENDED_EUCLIDEAN ** Analysis: 由于M比较小,所以可以用原始的定义求;但涉及逆元的求法! ** Time: 2011-04-17 18:43:08 **********************************/ #include long long exgcd(long long n, long long m, long long &x, long long &y) { if (m == 0) { x = 1; y = 0; return n; } long long g = exgcd(m, n % m, x, y); long long t = x - n / m * y; x = y; y = t; return g; } int main() { long long n, m, p, cases; scanf("%I64d", &cases); while (cases--) { scanf("%I64d%I64d%I64d", &n, &m, &p); long long x, y, ans = 1, i, j; for (i = n, j = m; j >= 1; i--, j--) { exgcd(j, p, x, y); x = x % p; if (x < 0) x += p; ans = (ans*i)%p; ans = (ans*x)%p; } printf("%I64d/n", ans); } }

注意中间结果可能会超过INT,故用要LONG LONG

你可能感兴趣的:(欧几里得、扩展的欧几里得算法)