一、同余的概念
所谓的同余,顾名思义,就是许多的数被一个数d去除,有相同的余数。d在数学上的称谓是模。如a=6,b=1,d=5,则我们说a和b是模d同余的。因为他们都有相同的余数1。数学上的记法为:a ≡ b (mod d)。也就是说,a除以d所得之余数与b除以d所得之余数是相等的,例如6 ≡ 1 (mod 5)。
同余有一些非常重要的性质,例如:
(1)如果a≡x (mod d),b≡m(mod d),则有 a+b≡x+m (mod d)。
(2)如果a≡x (mod d),b≡m(mod d),则有 a – b≡x-m (mod d)。
(3)如果a≡x (mod d),b≡m(mod d),则有 a * b≡x*m(mod d)。
(4)如果a≡b (mod d),则a-b整除d。
二、中国剩余定理
《孙子算经》中,有这样一道算术题:“今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?”按照今天的话来说:一个数除以3余2,除以5余3,除以7余2,求这个数。这样的问题,也有人称为“韩信点兵”。它形成了一类问题,也就是初等数论中的解同余式。宋朝数学家秦九韶在其所著的《数书九章》中对“物不知数”问题做出了完整系统的解答。后来这个结论被称为中国剩余定理。
用现代数学的语言来说明的话,中国剩余定理给出了以下的一元线性同余方程组:
有解的判定条件,并用构造法给出了在有解情况下解的具体形式。
三、一个例子
四、编程实现
分析:M=m1 * m2 *… * mk,L, J为任意整数。因为Mi能被m1, m2,…,mi-1, mi+1,…,mk整除(其中i+1<k),因此: Mi = (M/mi) *L。
又因为Mi除以mi余1,所以: Mi = mi*J + 1。即: mi*J + 1 = (M/mi)*L => (-mi)*J + (M/mi)*L = 1
而m1到mk这些数都是互质数,所以(-mi) 同 (M/mi)也是互质数.即 gcd(-mi, M/mi) = 1。
也就是说:(M/mi)*L + (-mi)*J = gcd(M/mi, -mi) => 其中-mi和M/mi都是已知的,J和L未知。
这就是经典扩展欧几里德定理的原型(由定理知J和L是唯一的, 因此,M1–>Mk有唯一解),按照扩展欧几里德定理求解即可:
扩展欧几里得函数实现如下:
void extend_Euclid(int a, int b, int &x, int &y){
//根据欧几里德定理
if(b == 0){//任意数与0的最大公约数为其本身。
x = 1;
y = 0;
}else{
int x1, y1;
extend_Euclid(b, a%b, x1, y1);
if(a*b < 0){//异号取反
x = - y1;
y = a/b*y1 - x1;
}else{//同号
x = y1;
y = x1 - a/b* y1;
}
}
}
最后完成的中国剩余定理实现函数如下:
int CRT(int a[],int m[],int k)
{
int M = 1;//最小公倍数
int result = 0;
for(int i = 0; i < k; i++){
M *= m[i];
}
for(int j = 0; j < k; j++){
int L, J;
extend_Euclid(M/m[j], -m[j], L, J);
//Mi = m[j] * J + 1;//(1), (1)和(2)这两个值应该是相等的。
int Mi = M/m[j] * L;//(2)
result += Mi*a[j];
}
//落在(0, M)之间,这么写是为了防止result初始为负数,确定不可能为负时可直接写成return result%M;
return (result % M + M) % M;
}
最后给一个测试用main函数:
int main(int argc, const char * argv[]) {
int a[3] = {2, 3, 2};
int m[3] = {3, 5, 7};
printf("Result: %d\n", CRT(a, m, 3));
return 0;
}
(本文完)