中国剩余定理,又名孙子定理,最早出现于《孙子算经》上,是一种用于求解一元线性同余方程组的算法。
引入例题:

“今有物不知其数,三三数之剩二(除以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;
}

先写到这里吧。。