算法|韩信点兵
完成阅读您将会了解中国余数定理的:
- 算法思想
- 实现步骤
- 实践范例(C++/Rust)
1. 算法思想
韩信点兵是后人描述中国余数定理(Chinese Remainder Theorem)营造的一个场景故事,其本质上是的中国余数定理的一个具体应用。中国余数定理最早由南北朝时期佚名作者在《孙子算经》中有所介绍,因此该定理也称作孙子定理。
中国余数定理在《孙子算经》中记载的背景问题原文为:
有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?
简单翻译为:有一个整数,被3除余2,被5除余3,被7除余2,试问这个数(最小)是多少?
该问题推广后等价于求以下线性同余方程组( Linear Congruence Equations )的整数解,
\[\left\{\begin{matrix}
x\equiv r_1 ~(mod~m_1) \\
x\equiv r_2 ~(mod~m_2) \\
x\equiv r_3 ~(mod~m_3) \\
\cdots \\
x\equiv r_n ~(mod~m_n)
\end{matrix}\right.
\]
其中\(m_1,m_2,\cdots,m_n\)两两互质。
中国余数定理是由中国古代科学家秦九韶针对这一类问题给出解法——大衍求一术。由于该定理的提出比西方数学家类似理论的发表提前了数百年,因此该定理只有中式命名且定理前冠有中国二字。
2. 实现步骤
设模数序列$M\in \mathbb{Z}+^n \(,且元素两两互质,那么与任意余数序列\)R\in \mathbb{Z}+^n$组成的线性同余方程组有解。
- 计算模数序列的连乘(product of a sequence),\(a=\prod_{i=1}^{n}M_i\),
- 方程组的解为\(x=\sum_{i=0}^{n}R_i\frac{a}{M_i}(\frac{a}{M_i})^{-1}|_{R_i} ~(mod~a)\),其中\((\frac{a}{M_i})^{-1}|_{R_i}\)为\(\frac{a}{M_i}\)的乘法逆元(Inverse Element)。逆元可以通过定义由拓展欧几里得(Extended Euclid)算法求解,或通过费马小定理(Fermat's little theorem)求幂得出。本文实践中使用自叠加方式暴力试凑逆元,实际上效率并不低,且无需两两互质条件。
3. 范例实践(C++/Rust)
问题描述:
给定模数序列M与对应的余数序列R,求其构成的线性同余方程组的解x。
输入:M,R
输出:x
解答思路:
运用中国余数定理求解。
伪代码1:中国余数定理
变量说明:
\[\begin{aligned}
&CRT(M,R)\\
&~~~~~~a\leftarrow \prod_{i=1}^{M.size}M[i] \\
&~~~~~~P\leftarrow \frac{a}{M} \\
&~~~~~~for~~~i\leftarrow 0~~~to~~~M.size \\
&~~~~~~~~~~~~p_i\leftarrow P[i] \\
&~~~~~~~~~~~~m_i\leftarrow M[i] \\
&~~~~~~~~~~~~r_i\leftarrow R[i] \\
&~~~~~~~~~~~~while~~~P[i] \neq r_i~(mod~m_i)\\
&~~~~~~~ ~~~~~~~~~~~P[i]\leftarrow P[i]+p_i \\
&~~~~~~return \sum_{i=0}^{M.size}P[i] ~(mod~a)
\end{aligned}
\]
C++解答:
auto CRT(unsigned *M, unsigned *R, auto len) {
auto a = std::accumulate(M, M + len, 1, [](auto n1, auto n2) { return n1 * n2; });
unsigned P[len];
std::transform(M, M + len, P, [a](auto i) { return a / i; });
for (auto i = 0; i < len; ++i) {
auto p_i = P[i], m_i = M[i], r_i = R[i];
while (P[i] % m_i != r_i){P[i]+=p_i;}
}
return std::accumulate(P, P + len, 0) % a;
}
Rust解答:
pub fn CRT(M: &[u32], R: &[u32], size: usize) -> u32 {
let a: u32 = M.iter().product();
let mut P: Vec<u32> = M.iter().map(|x| a / x).collect();
for i in 0..size {
let (p_i, r_i, m_i) = (P[i], R[i], M[i]);
while P[i] % m_i != r_i { P[i] += p_i; }
}
P.iter().sum::<u32>() % a
}
4. 自我测试
伪代码实践:
N/A
LeetCode选荐:
N/A
让每一天足够精致,期待与您的再次相遇! ^_^

浙公网安备 33010602011771号