欧几里德算法和扩展欧几里德算法
欧几里德算法
欧几里德算法又称辗转相除法,用于计算两个整数a,b的最大公约数。其计算原理依赖于下面的定理:
定理:gcd(a,b) = gcd(b,a mod b)
证明:a可以表示成a = kb + r,则r = a mod b
假设d是a,b的一个公约数,则有
d|a, d|b,而r = a - kb,因此d|r
因此d是(b,a mod b)的公约数
假设d 是(b,a mod b)的公约数,则
d | b , d |r ,但是a = kb +r
因此d也是(a,b)的公约数
因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证
欧几里德算法就是根据这个原理来做的,其算法用C++语言描述为:
void swap(int & a, int & b)
{
int c = a;
a = b;
b = c;
}
int gcd(int a,int b)
{
if(0 == a )
{
return b;
}
if( 0 == b)
{
return a;
}
if(a > b)
{
swap(a,b);
}
int c;
for(c = a % b ; c > 0 ; c = a % b)
{
a = b;
b = c;
}
return b;
}
模P乘法逆元
对于整数a、p,如果存在整数b,满足ab mod p =1,则说,b是a的模p乘法逆元。
定理:a存在模p的乘法逆元的充要条件是gcd(a,p) = 1
证明:
首先证明充分性
如果gcd(a,p) = 1,根据欧拉定理,aφ(p) ≡ 1 mod p,因此
显然aφ(p)-1 mod p是a的模p乘法逆元。
再证明必要性
假设存在a模p的乘法逆元为b
ab ≡ 1 mod p
则ab = kp +1 ,所以1 = ab - kp
因为gcd(a,p) = d
所以d | 1
所以d只能为1
扩展欧几里德算法
扩展欧几里德算法不但能计算(a,b)的最大公约数,而且能计算a模b及b模a的乘法逆元,用C语言描述如下:
int gcd(int a, int b , int& ar,int & br)
{
int x1,x2,x3;
int y1,y2,y3;
int t1,t2,t3;
if(0 == a)
{//有一个数为0,就不存在乘法逆元
ar = 0;
br = 0 ;
return b;
}
if(0 == b)
{
ar = 0;
br = 0 ;
return a;
}
x1 = 1;
x2 = 0;
x3 = a;
y1 = 0;
y2 = 1;
y3 = b;
int k;
for( t3 = x3 % y3 ; t3 != 0 ; t3 = x3 % y3)
{
k = x3 / y3;
t2 = x2 - k * y2;
t1 = x1 - k * y1;
x1 = y1;
x1 = y2;
x3 = y3;
y1 = t1;
y2 = t2;
y3 = t3;
}
if( y3 == 1)
{
//有乘法逆元
ar = y2;
br = x1;
return 1;
}else{
//公约数不为1,无乘法逆元
ar = 0;
br = 0;
return y3;
}
}
扩展欧几里德算法对于最大公约数的计算和普通欧几里德算法是一致的。计算乘法逆元则显得很难明白。我想了半个小时才想出证明他的方法。
首先重复拙作整除中的一个论断:
如果gcd(a,b)=d,则存在m,n,使得d = ma + nb,称呼这种关系为a、b组合整数d,m,n称为组合系数。当d=1时,有 ma + nb = 1 ,此时可以看出m是a模b的乘法逆元,n是b模a的乘法逆元。
为了证明上面的结论,我们把上述计算中xi、yi看成ti的迭代初始值,考察一组数(t1,t2,t3),用归纳法证明:当通过扩展欧几里德算法计算后,每一行都满足a×t1 + b×t2 = t3
第一行:1 × a + 0 × b = a成立
第二行:0 × a + 1 × b = b成立
假设前k行都成立,考察第k+1行
对于k-1行和k行有
t1(k-1) t2(k-1) t3(k-1)
t1(k) t2(k) t3(k)
分别满足:
t1(k-1) × a + t2(k-1) × b = t3(k-1)
t1(k) × a + t2(k) × b = t3(k)
根据扩展欧几里德算法,假设t3(k-1) = j t3(k) + r
则:
t3(k+1) = r
t2(k+1) = t2(k-1) - j × t2(k)
t1(k+1) = t1(k-1) - j × t1(k)
则
t1(k+1) × a + t2(k+1) × b
=t1(k-1) × a - j × t1(k) × a +
t2(k-1) × b - j × t2(k) × b
= t3(k-1) - j t3(k) = r
= t3(k+1)
得证
因此,当最终t3迭代计算到1时,有t1× a + t2 × b = 1,显然,t1是a模b的乘法逆元,t2是b模a的乘法逆元。
扩展欧几里德算法求的是二元一次方程ax+by=c,在a,b,c已知的情况下x的最小整数值,
扩展欧几里德算法:
- int exgcd(int a,int b,int &X,int &Y)
- {
- if(b==0)
- {
- X=1;
- Y=0;
- return a;
- }
- int d=exgcd(b,a%b,X,Y);
- int t=X;
- X=Y;
- Y=t-(a/b)*Y;
- return d;
- }
int r=exgcd(a,b,X,Y)
当c%r==0时:
b/=r;
c/=r;
x的最小整数值x1=(c*X%b+b)%b
否则不存在x的最小整数解。
题目poj 1061
这题先列出式子,设跳了xi步之后他们会相遇。则有公式
(xi*m+x)-(xi*n+y)=kl(k=1,2,3.....)或者(xi*n+y)-(xi*m+x)=kl(k=1,2,3.....)
化简成xi(m-n)+kl=y-x或者xi(n-m)+kl=x-y。
刚好符合扩展欧几里德的公式要求,a=m-n(m>n)或者n-m(n>m),b=k,c=y-x(m>n)或者x-y(n>m),这样套扩展欧几里德公式求xi的最小正整数就行了,代码
- #include<iostream>
- #include<cmath>
- using namespace std;
- __int64 exgcd(__int64 a,__int64 b,__int64 &X,__int64 &Y)
- {
- if(b==0)
- {
- X=1;
- Y=0;
- return a;
- }
- __int64 d=exgcd(b,a%b,X,Y);
- __int64 t=X;
- X=Y;
- Y=t-(a/b)*Y;
- return d;
- }
- int main()
- {
- __int64 x,y,m,n,L,X,Y,a,b,r,x1,c;
- while(scanf("%I64d%I64d%I64d%I64d%I64d",&x,&y,&m,&n,&L)!=EOF)
- {
- a=m>n?m-n:n-m;
- b=L;
- c=m>n?y-x:x-y;
- r=exgcd(a,b,X,Y);
- if(c%r==0)
- {
- b/=r;
- c/=r;
- x1=(c*X%b+b)%b;
- printf("%I64d/n", x1);
- }
- else
- cout<<"Impossible"<<endl;
- }
- return 0;
- }
最大公约数(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 <stdio.h>
- 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;
(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 排列
[cpp:collapse] + expand sourceview plaincopy
中国剩余定理:
设总数为n,模a得x,模b得y,模c得z,若已知x,y,z,让求出最小的n。
则n=(x*a1+y*b1+z*c1)%d;
其中a1=y*z中的倍数中模a等于1的最小的数;
b1=x*z中的倍数中模b等于1的最小的数;
c1=x*y中的倍数中模c等于1的最小的数;
d=a,b,c的最小公倍数。
中国剩余定理原版之韩信点兵版:
传说韩信点兵时发明的算法。设士兵总数为n,模3得x,模5得y,模7得z,若已知x,y,z,让求出最小的n。
则n=(x*70+y*21+z*15)%105;
可以用下面的小诗帮助记忆。
三人成行七十稀;70为35(5×7)的倍数中模3等于1的最小的数;
五树梅花廿一枝;21为21(3×7)的倍数中模5等于1的最小的数;
七子团圆月正半;15为15(3×5)的倍数中模7等于1的最小的数;
除百零五便得之。105为3,5,7的最小公倍数。
如果不是3,5,7用同样的方法求解。
题目poj 1006
设第n天同时出现3个巅峰,有
n=(p*5544+e*14421+i*1288)%21252;
题目要求n-d,这里只需要注意可能d>n,因为他们3个同时出现的一个周期是21252,所以当d>n,那么n-d+21252就是所求。