首先说一下,欧几里得算法:求两个数的最大公约数
教材的写法是:
1 //输入要求a>=b>=0 2 unsigned euclid(unsigned a,unsigned b){ 3 if(b==0) return a; 4 return euclid(b,a%b); 5 }
当然也可以改成非递归的迭代版本:
1 int Gcd(int a, int b) 2 { 3 while(b != 0) 4 { 5 int r = b; 6 b = a % b; 7 a = r; 8 } 9 return a; 10 }
上个世纪60年代研究人员对这个古老的算法进行了改进,称为二进制欧几里得算法:
基于的原理是:
- 若 N 和 M 是偶数,gcd(N, M) = 2 gcd(N/2, M/2) ,
- 若 N 是偶数而 M 是奇数,那么 gcd(N, M) = gcd(N/2, M) ,
- 若 N 和 M 都是奇数,那么 (由于 N-M 是偶数) |N-M| < max(N,M) 。用 |N-M| 替换两者中的较大者
该算法称为 二进制 因为,与原来的不同,它不使用通常的除法而只使用除以2的。由于计算机数字总是用二进制系统表示,你不会对有一种特殊的 机器指令 以高效的方式实现 除以 2而感到惊讶。这称为 右移位,其中最右一位被舍弃,其他的位向右移动一位,而最左一位被设为0。
另一个顺手拈来的操作是按位与 & 。对任意整数 N 来说,N & 1 是 1 或 0 。当且仅当 N 是奇数时它为 1 。按位与也在硬件中实现,例如,作为 机器指令
递归版本:
1 unsigned int gcd(unsigned int u, unsigned int v) 2 { 3 // simple cases (termination) 4 if (u == v) return u; 5 if (u == 0) return v; 6 if (v == 0) return u; 7 8 // look for factors of 2 9 if (~u & 1) // u is even 10 { 11 if (v & 1) return gcd(u >> 1, v); // u is even,v is odd 12 else return gcd(u >> 1, v >> 1) << 1; // both u and v are even 13 14 } 15 if (~v & 1) return gcd(u, v >> 1); // u is odd, v is even 16 17 18 // both u and v are odd,reduce larger argument 19 if (u > v) return gcd((u - v) >> 1, v); 20 21 return gcd((v - u) >> 1, u); 22 }
当然也可以改成迭代版本:
1 unsigned int gcd2(unsigned int u, unsigned int v) 2 { 3 int shift; 4 5 /* GCD(0,v) == v; GCD(u,0) == u, GCD(0,0) == 0 */ 6 if (u == 0) return v; 7 if (v == 0) return u; 8 9 /* Let shift := lg K, where K is the greatest power of 2 10 dividing both u and v. */ 11 for (shift = 0; ((u | v) & 1) == 0; ++shift) { 12 u >>= 1; 13 v >>= 1; 14 } 15 16 while ((u & 1) == 0) 17 u >>= 1; 18 19 /* From here on, u is always odd. */ 20 do { 21 /* remove all factors of 2 in v -- they are not common */ 22 /* note: v is not zero, so while will terminate */ 23 while ((v & 1) == 0) /* Loop X */ 24 v >>= 1; 25 26 /* Now u and v are both odd. Swap if necessary so u <= v, 27 then set v = v - u (which is even). For bignums, the 28 swapping is just pointer movement, and the subtraction 29 can be done in-place. */ 30 if (u > v) { 31 unsigned int t = v; v = u; u = t;} // Swap u and v. 32 v = v - u; // Here v >= u. 33 } while (v != 0); 34 35 /* restore common factors of 2 */ 36 return u << shift; 37 }
拓展欧几里得的算法是:
欧几里得定理告诉我们,只要能找到整数x和y,使得ax+by=d,则d就是a和b的最大公因子。而且如果d是a和b的最大公因子,则d一定可以表示为ax+by的形式。
运用下列函数的前提是gcd(a,b)=d,即d是a和b的最大公约数,则该函数可以计算得到ax+by=d中的x值。
1 //要求是gcd(a,b)=d 2 int e_gcd(int a,int b,int &x,int &y){ 3 if(b==0){ 4 x=1; 5 y=0; 6 return a; 7 } 8 int ans=e_gcd(b,a%b,x,y); 9 int temp=x; 10 x=y; 11 y=temp-a/b*y; 12 return ans; 13 }
拓展欧几里得算法的一个运用是计算模n逆元:如果有ax=1(mod N),那么我们称x是a的模N逆元。
1 //计算得到 “a模m逆元” 2 int cal(int a,int m){ 3 int x,y; 4 int gcd=e_gcd(a,m,x,y); 5 if(1%gcd!=0) return -1; 6 x*=1/gcd; 7 m=abs(m); 8 int ans=x%m; 9 if(ans<=0) ans+=m; 10 return ans; 11 }
其实,“a的模N逆元”,主要在RSA中计算解密密钥时用的较多。关于RSA的细节,这里不赘述,详情参加另外一篇博客。在计算RSA中加密时,需要用到快速幂取模算法,详见九月份的一篇博客。
贴一个应用:
ural Idempotents
Description
The number x is called an idempotent modulo n if
x* x = x (mod n)
Write the program to find all idempotents modulo n, where n is a product of two distinct primes p and q.
Input
First line contains the number k of test cases to consider (1 ≤ k ≤ 1000). Each of the following k lines contains one number n < 10 9.
Output
Write on the i-th line all idempotents of i-th test case in increasing order. Only nonnegative solutions bounded by n should be printed.
Sample Input
input | output |
---|---|
3 6 15 910186311 |
0 1 3 4 0 1 6 10 0 1 303395437 606790875 |
AC代码:
1 #include <iostream> 2 using namespace std; 3 4 5 int getp(int n){ //得到大于1的最小的因数 6 int p=2; 7 while(n%p!=0){ 8 p++; 9 } 10 return p; 11 } 12 13 void e_gcd(int a,int b,int &x,int &y){ 14 if(b==0){ 15 x=1; 16 y=0; 17 return ; 18 } 19 e_gcd(b,a%b,x,y); 20 int temp=x; 21 x=y; 22 y=temp-a/b*y; 23 return ; 24 } 25 26 int main() 27 { 28 int k; 29 cin>>k; 30 while(k--) 31 { 32 int n; 33 cin>>n; 34 int p=getp(n),q=n/p; 35 int x,y; 36 e_gcd(p,q,x,y) ; 37 int x1=x*p; 38 if(x1<0) x1+=n; 39 40 e_gcd(q,p,x,y) ; 41 int x2=x*q; 42 if(x2<0) x2+=n; 43 if(x2<x1){ 44 int temp=x2; 45 x2=x1; 46 x1=temp; 47 } 48 cout<<0<<" "<<1<<" "<<x1<<" "<<x2<<endl; 49 } 50 51 }