在《孙子算经》里有这么一段话:“今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何? ”意思就是,一个数除以3余2,除以5余3,除以7余2。
用数学公式描述,就是:
x
≡
2
(
m
o
d
3
)
x
≡
3
(
m
o
d
5
)
x
≡
2
(
m
o
d
7
)
x \equiv 2\;(mod\;3)\\ x \equiv 3\;(mod\;5)\\ x \equiv 2\;(mod\;7)
x≡2(mod3)x≡3(mod5)x≡2(mod7)
≡
\equiv
≡是等价符号,整个公式表示同余。
x
≡
2
(
m
o
d
3
)
x \equiv 2\;(mod\;3)
x≡2(mod3)表示x和2除于3的余数是相等的。很明显,解不止一个,23就是其中一个解。3、5、7的最小公倍数是105,对于所有除于105余23的正整数,也就是23+105N(N取任意正整数)都是这个方程的解。所以解集就是同余类[23]105,最小公倍数是容易计算出来的,但最重要的是我们要求这个同余类的余数23。
如果用brute-force算法,从1到105之间循环,每个数用同余方程组验证,那肯定能算出来。如果开发时间比较紧,brute-force是最好的办法。
但是实际上,这个问题早就有答案了。用数学方式描述下计算过程就是:
假设线性同余方程组为
x
≡
a
1
(
m
o
d
m
1
)
x
≡
a
2
(
m
o
d
m
2
)
x
≡
a
3
(
m
o
d
m
3
)
⋮
x
≡
a
n
(
m
o
d
m
n
)
x \equiv a_1\;(mod\;m_1)\\ x \equiv a_2\;(mod\;m_2)\\ x \equiv a_3\;(mod\;m_3)\\ \vdots\\ x \equiv a_n\;(mod\;m_n)\\
x≡a1(modm1)x≡a2(modm2)x≡a3(modm3)⋮x≡an(modmn)
假设M为m1到Mn的最小公倍数。bi=M/Mi。再定义ci=b1-1(mod Mi).这里的-1符号不是-1次方的意思,而是在Mi下的模乘逆元的意思。那么结果就是
x
=
(
∑
i
=
0
n
a
i
b
i
c
i
)
(
m
o
d
M
)
x=(\sum_{i=0}^na_ib_ic_i)(mod\;M)\\
x=(i=0∑naibici)(modM)
公式,我这里就不花时间证明了,知乎、CSDN有大佬的证明过程,这里我给出了python代码:
# _*_ coding:utf-8 _*_
from com.youngthing.mathalgorithm.extend_euclid import modular_inverse
def crt(a_array, m_array):
# 假设m互质,这里就不做质因数分解了
m_pi = 1
for m in m_array:
m_pi *= m
x = 0
for i, a in enumerate(a_array):
m = m_array[i]
b = m_pi // m
x += a * (b) * modular_inverse(b, m)
return x % m_pi
if __name__ == '__main__':
print(crt([2, 3, 2], [3, 5, 7]))
测试结果为23。代码中用到的模乘逆元,可以看本专栏另一个篇文章:3 模乘逆元-扩展欧几里得法