中国剩余定理,又名孙子定理,最早出现于《孙子算经》上,是一种用于求解一元线性同余方程组的算法。
引入例题:
“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?” ——《孙子算经》
翻译成现代汉语就是:有一个整数x,x % 3 = 2, x % 5 = 3, x % 7 = 2, 求x。
老祖宗的解法是这样的:
1.找出三个数:从3和5的公倍数中找出被7除余1的最小数15,从3和7的公倍数中找出被5除余1 的最小数21,最后从5和7的公倍数中找出除3余1的最小数70。
2.用15乘以2(2为最终结果除以7的余数),用21乘以3(3为最终结果除以5的余数),同理,用70乘以2(2为最终结果除以3的余数),然后把三个乘积相加15∗2+21∗3+70∗2得到和233。
3.用233除以3,5,7三个数的最小公倍数105,得到余数23,即233%105=23。这个余数23就是符合条件的最小数(加整数倍lcm(3, 5, 7)后依然为解)。
真的是感叹于老祖宗是如何想到这种巧妙的办法的阿!
基本原理:
有一元线性同余方程组
x ≡ a1 (mod m1)
x ≡ a2 (mod m2)
……
x ≡ an (mod mn)
其中任意mi,mj(1<=mi≠mj<=n)互质
令m = lcm(m1, m2, m3, … , mn),
设
n1 % m1 = a1,
n2 % m2 = a2
……
nn % mn = an
若我们想让(n1+n1+ … +nn) 分别% mi = ai均可满足的话,则可让ni % mi = ai,而剩下的任何nj % mi = 0,如果这样的条件对于任意的1<=i<=n均满足的话,我们的目标便可达成,而此时的n1+n2+ … +nn便是方程组的一个解。
而ni可表示为Ni * ai,如果Ni % mi = 1的话。
此时关于n的和式就变成了a1 * N1 + a2 * N2 + … + an * Nn。
这样,问题就转化成了求Ni。
令Mi = lcm(m1, m2, … , mi-1, mi+1, mi+2, … , mn),因为mi中任意两数互质,所以Mi = m/mi.
由同余定理我们知道,m1, m2, … , mi-1, mi+1, … ,mn均为Ni的因子, 所以Mi也是Ni因子,也就是Ni = Mi * R,Mi我们是知道的,所以此时问题转化为求R。
(Mi * R) % mi = 1 <==> Mi * R ≡ 1 (mod mi) <==> Mi * R + mi * y = 1
mi中任意两数互质,所以Mi与mi互质,即gcd(Mi, mi) = 1,上式为贝祖等式,故可用exgcd求出一组R0与y0,此时Ni = Mi * R0,ni = Mi * R0 * ai。
依次求出n1, n2, … ,nn,再求出它们的和,便是方程组的一个解x0,x0 % m便是其最小正解。
下为配套代码(C++):上面几个函数为了方便复习顺手写上去的。。
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a%b);
}
int lcm(int a, int b)
{
return a*b / gcd(a, b);
}
int exgcd(int a, int b, int& x, int &y)
{
if(b==0)
{
x = 1;
y = 0;
return a;
}
else
{
int t = exgcd(b, a%b, y, x);
y -=a/b * x;
return t;
}
}
int CRT(int n, int a[], int m[])
{
int l_cm = 1;
for(int i = 0; i < n; i++)
l_cm *= m[i];
int N = 0;
for(int i = 0; i < n; i++)
{
int M = l_cm/m[i];
int R, Y;
exgcd(M, m[i], R, Y);
N += M*R*a[i];
}
return N%l_cm;
}
先写到这里吧。。