参考:
中国剩余定理:
http://itdocument.com/7701006441/
http://www.cppblog.com/qywyh/archive/2007/08/27/30943.aspx
扩展欧几里得算法:
中国剩余定理介绍
在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3 余2),
五五数之剩三(除以5 余3),七七数之剩二(除以7 余2),问物几何?”这个问题称为“孙
子问题”,该问题的一般解法国际上称为“中国剩余定理”。具体解法分三步:
找出三个数:从3 和5 的公倍数中找出被7 除余1 的最小数15,从3 和7 的公倍数中找出被
5 除余1 的最小数21,最后从5 和7 的公倍数中找出除3 余1 的最小数70。
用15 乘以2(2 为最终结果除以7 的余数),用21 乘以3(3 为最终结果除以5 的余数),同
理,用70 乘以2(2 为最终结果除以3 的余数),然后把三个乘积相加(15*2+21*3+70*2)得
到和233。
用 233 除以 3,5,7 三个数的最小公倍数 105,得到余数 23,即 233%105=23。这个余数 23
就是符合条件的最小数。
就这么简单。我们在感叹神奇的同时不禁想知道古人是如何想到这个方法的,有什么基
本的数学依据吗?
中国剩余定理分析
我们将“孙子问题”拆分成几个简单的小问题,从零开始,试图揣测古人是如何推导出
这个解法的。
首先,我们假设n1 是满足除以3 余2 的一个数,比如2,5,8 等等,也就是满足3*k+2
(k>=0)的一个任意数。同样,我们假设 n2 是满足除以 5 余 3 的一个数,n3 是满足除以 7
余2 的一个数。
有了前面的假设,我们先从n1这个角度出发,已知n1满足除以3余2,能不能使得 n1+n2
的和仍然满足除以3 余2?进而使得n1+n2+n3 的和仍然满足除以3 余2?
这就牵涉到一个最基本数学定理,如果有a%b=c,则有(a+kb)%b=c(k 为非零整数),换句
话说,如果一个除法运算的余数为 c,那么被除数与 k 倍的除数相加(或相减)的和(差)
再与除数相除,余数不变。这个是很好证明的。
以此定理为依据,如果n2 是3 的倍数,n1+n2 就依然满足除以3 余2。同理,如果n3
也是3 的倍数,那么n1+n2+n3 的和就满足除以3 余2。这是从n1 的角度考虑的,再从n2,
n3 的角度出发,我们可推导出以下三点:
为使n1+n2+n3 的和满足除以3 余2,n2 和n3 必须是3 的倍数。
为使n1+n2+n3 的和满足除以5 余3,n1 和n3 必须是5 的倍数。
为使n1+n2+n3 的和满足除以7 余2,n1 和n2 必须是7 的倍数。
因此,为使n1+n2+n3 的和作为“孙子问题”的一个最终解,需满足:
n1 除以3 余2,且是5 和7 的公倍数。
n2 除以5 余3,且是3 和7 的公倍数。
n3 除以7 余2,且是3 和5 的公倍数。
所以,孙子问题解法的本质是从5 和7 的公倍数中找一个除以3 余2 的数n1,从3 和7
的公倍数中找一个除以5 余3 的数n2,从3 和5 的公倍数中找一个除以7 余2 的数n3,再将 三个数相加得到解。
在求n1,n2,n3 时又用了一个小技巧,以n1 为例,并非从5 和7 的公 倍数中直接找一个除以3 余2 的数,而是先找一个除以3 余1 的数,再乘以2。
这里又有一个数学公式,如果a%b=c,那么(a*k)%b=(a%b+a%b+„+a%b)%b=(c+c+„+c)%b=kc%b(k>0),
也就是说,如果一个除法的余数为c,那么被除数的k 倍与除数相除的余数为kc。展开式中
已证明。
最后,我们还要清楚一点,n1+n2+n3 只是问题的一个解,并不是最小的解。如何得到最
小解?我们只需要从中最大限度的减掉掉3,5,7 的公倍数105 即可。道理就是前面讲过的
定理“如果a%b=c,则有(a-kb)%b=c”。所以(n1+n2+n3)%105 就是最终的最小解。
总结
经过分析发现,中国剩余定理的孙子解法并没有什么高深的技巧,就是以下两个基本数学
定理的灵活运用:
如果 a%b=c , 则有 (a+kb)%b=c (k 为非零整数)。
如果 a%b=c,那么 (a*k)%b=kc%b (k 为大于零的整数)。
推广到一般情况:
例如下面的一元线性同余方程组:
x ≡ a1 (mod m1)
x ≡ a2 (mod m2)
x ≡ a3 (mod m3)
… …
x ≡ an (mod mn)
假设整数m1, m2, m3……, mn两两互质,m=m1*…*mn,则对于任意的整数a1, a2, a3….an, X有解。
求解X过程:X=(除以m1余a1的a2,a3…an的公倍数+…+除以mj余aj的m1…mj-1, mj+1…mn的公倍数+除以mn余an的m1,…, mn-1的公倍数)%LCM(m1,…, mn)= [(除以m1余a1的a2,a3…an的公倍数)%LCM(a1,…,an)+…+(除以mj余aj的m1…mj-1, mj+1…mn的公倍数)%LCM(m1,…, mn)+(除以mn余an的m1,…, mn-1的公倍数) %LCM(m1,…, mn)]%LCM(m1,…, mn)。
问题转换为:找到除以mj余aj的m1…mj-1, mj+1…mn的公倍数。这里有个技巧,先找到除以mj余1的m1…mj-1, mj+1…mn的公倍数Nj,然后Nj*aj即可。
问题转换为:求Nj(m1…mj-1, mj+1…mn的最小公倍数Mj=m/mj,因为余数m1…mn互为质数,所以Mj是最小公倍数)。
问题转换为:求Mj的某个倍数Nj=Mj*R,使得除以Nj余1。
问题转换为:求R,使得Mj*R≡1(mod mj),即求Mj的乘法逆元。
求乘法逆元(用扩展欧几里得算法):Mj*R+mj*y=1,变量是R和y,调用函数exGcd(Mj,mj,R,y)即可。
最终X=西格玛(Rj*Mj)*a[j]%m
代码,变量名和分析过程使用的一致:
long long exGcd(long long a,long long b,long long &x,long long &y){
if(0==b) {
x=1;y=0;return a;
}
long long gcd=exGcd(b,a%b,y,x);
y-=a/b*x; return gcd;
}
long long lmes(){
long long X=1,R,y,X,Mi;
for(int i=0;i<n;i++) m*=a[i];//n[i]是第i个余数
for(int i=0;i<n;i++) {
Mi=m/n[i];
exGcd(Mi,a[i],R,y);
X+=(a[i]*Mi*R)%m;
}
return (X+m)%m;//X可能是负数
}
m1,…,mn不互质的情形:合并方程。
long long Rem(long long a[],long longm[],int num){
long long n1=n[0],a1=a[0],n2,a2,k1,k2,x0,gcd,c;
for(int i=1;i<num;i++){
n2=n[i],a2=a[i];
c=a2-a1;
gcd=exGcd(n1,n2,k1,k2);//解得:n1*k1+n2*k2=gcd(n1,n2)
if(c%gcd){
flag=1;
return 0;//无解
}
x0=c/gcd*k1;//n1*x0+n2*(c/gcd*k2)=c PS:k1/gcd*c错误!
t=n2/gcd;
x0=(x0%t+t)%t;//求n1*x0+n2*y=c的x0的最小解
a1+=n1*x0;
n1=n2/gcd*n1;
}
return a1;
}