算法|韩信点兵

完成阅读您将会了解中国余数定理的:

  1. 算法思想
  2. 实现步骤
  3. 实践范例(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$组成的线性同余方程组有解。

  1. 计算模数序列的连乘product of a sequence),\(a=\prod_{i=1}^{n}M_i\)
  2. 方程组的解为\(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

让每一天足够精致,期待与您的再次相遇! ^_^

posted @ 2021-07-06 19:39  我的名字被占用  阅读(565)  评论(0)    收藏  举报