关于如何从零推出中国剩余算法
首先我们需要了解什么是中国剩余定理。
定理 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)

浙公网安备 33010602011771号