关于如何从零推出中国剩余算法

首先我们需要了解什么是中国剩余定理。

定理 1(中国剩余定理) 如果有 \(n=r_1r_2r_3\cdots r_i \cdots r_k\), 其中 \(r_i\) 两两互质,则对于同余方程组 \(c_i \equiv m_i \pmod {r_i}\), 在模 \(n\) 意义下有唯一解 \(a\)

证明不加赘述。

中国剩余定理其实和向量基本定理很像,它定义了一组基底\(r_i\),和投影系数\(c_i\),并声明这样就足够唯一确定向量\(a\).

中国剩余定理的考察主要是给出同余方程组,让你求\(a\). 让我们来思考怎样解决这个问题。

我们可以很快注意到我们可以用类似拉格朗日插值的思想解决问题。或者说,对于每个同余方程\(a \equiv c_i \pmod r_i\),我们构造出一个特殊的数\(t_i\),使它满足 \(t_i \equiv c_i \pmod {r_i}\) 的同时对于其他的模数 \(r_j\), 有 \(t_i \equiv 0 \pmod {r_j}\),再将所有 \(t_i\) 加起来,就得到了 \(a\) 满足所有方程。

这个特殊的数 \(t_i\) 如何构造呢?我们一步一步来。

首先要满足 \(t_i \equiv 0 \pmod {r_j}\). 我们注意到,这等价于 \(r_j|t_i\). 也就是说,\(t_i\) 必须含有因数 \(r_j\). 所以我们不妨先令 \(t_i=m_i=\prod_{j\neq i}{r_j}\)。这样我们后续乘上任何东西都会保证这条性质成立。剔除掉 \(r_i\) 是因为如果乘了那一定有 \(t_i \equiv 0 \ne c_i \pmod {r_i}\)

其次要满足 \(t_i \equiv c_i \pmod {r_i}\). 这怎么办呢?我们先考察我们定义的 \(m_i\) 在模 \(r_i\) 下有何性质。然而我们发现没有性质。
所以我们简单粗暴地乘它的逆元来归一化。找出 \(m_i\)\(r_i\) 下的逆元 \(\bar{m_i}\),将 \(\bar{m_i}\) 乘我们定义的 \(t_i\)(\(m_i\))。此时有 \(t_i = \bar{m_i}m_i \equiv 1 \pmod {r_i}\)(由逆元的定义).
再然后乘上 \(c_i\) 就可以了。我们最后得到的 \(t_i = c_i\bar{m_i}m_i\)

\(a \equiv \sum_{i}c_i\bar{m_i}m_i \pmod{n}\)

我们思考如何把它变成高效的代码。

首先\(m_i = \prod_{j\neq i}{n_i} = n / r_i\). \(\Theta(1)\)
其次用扩展欧几里得算法求出逆元,这是\(\Theta(\log n)\)的。
CPP 代码如下:

int CRT(int k, int r[], int c[]) {
  int n = 1, ret = 0;
  for (int i = 1; i <= k; i++) n = n * r[i]; // 计算 n
  for (int i = 1; i <= k; i++) {
    int m = n / r[i], mbar, _;
    exgcd(m, r[i], mbar, _);  // mbar * m mod r[i] = 1
    ans = (ans + c[i] * mbar * m % n) % n;
  }
  return (ans % n + n) % n;
}

(代码改编自oiwiki)

posted @ 2025-09-03 22:10  奇怪的知识增加了!  阅读(10)  评论(0)    收藏  举报