看了几天挑战编程的数论,颇有感触,尤其是欧几里得算法,特此记下笔记(毕竟书是借的)。
整除:对于整数a和b, 若存在整数k使得a = bk, 则称b整除(divides)a(用b|a来表示)。b|a也可以说成b是a的约数,或者a是b的倍数(multiple)。
唯一分解定理:x能唯一的表示成它的素因数的乘积。
如果两个整数的最大公约数(greatest common divisor)(也称gcd)是1,称二者是互素(relatively prime)的。
Euclid算法:gcd(a,b)=gcd(b,a%b)
Euclid算法的证明:
1.如果b|a,则gcd(a,b)= b。因为如果b整除a,则存在整数k,使得a=bk,因此gcd(bk,b)=b。
2.如果存在整数t和r,使得a=bt+r,则gcd(a,b)=gcd(b,r)。因为gcd(a,b)=gcd(bt+r,b),由于bt是b所有约数的倍数,所以a和b的所有公约数都应该能整除r。
Euclid算法还能找出两个x和y,使得
a*x+b*y=gcd(a,b)
求法:
我们知道gcd(a,b)=gcd(b,a'),其中a‘=a-b*floor(a/b)。根据数学归纳原理,假设我们已经找出整数x’和y‘,使得
b*x'+a'*y'=gcd(a,b)
把a'的表达式带入上式,得到:
b*x'+(a-b*floor(a/b))*y'=gcd(a,b)
联立得:
a*x+b*y=b*x'+(a-b*floor(a/b))*y'
将等号右边整理下:
a*x+b*y=a*y'+b*(x'-floor(a/b)*y')
得到
x = y', y = x'-floor(a/b)*y'
由此我们得到了x和y的表达式。
算法边界的情况:
a*1+0*0=gcd(a,0)
代码如下:
/* Find the gcd(p, q) and x,y such that p*x + q*y = gcd(p, q) */ //例如对于两个数34398和2132, 有34398*15+2132*(-242)=26 #include <stdio.h> #include <stdlib.h> #include <string.h> long gcd(long p, long q, long *x, long *y){ long nx, ny, g; if(q > p) return (gcd(q, p, y, x)); if(q == 0){ *x = 1; *y = 0; return (p); } g = gcd(q, p%q, &nx, &ny); *x = ny; *y = (nx - (p/q)*ny); return g; } int main(){ long p, q, x, y, g; p = 34398; q = 2132; g = gcd(p, q, &x, &y); printf("%ld*%ld+%ld*%ld=%ld\n", p, x, q, y, g); return 0; }
《算法竞赛入门经典——训练指南》上的代码(和上面一样,只是更简洁):
void gcd(LL a, LL b, LL &d, LL &x, LL &y) { if(!b) { d = a; x = 1; y = 0; } else { gcd(b, a%b, d, y, x); y -= x*(a/b); } }
d 代表 a 和 b 的最大公约数, 找出 x、y, 使得 ax+by=d。在此前提下 |x|+|y| 取最小值。
//2013-04-02 20:20:12
//2013-05-27 19:50:24 增加《训练指南》上的代码。