摘要 (Abstract) 背景与动机 (Motivation) 问题定义 (Problem Definition) 一句话算法 关键思路 (Key Idea) 算法步骤 (Algorithm Steps) 算法证明 复杂度分析 代码实现 测试用例 经典例题 1. 洛谷 P1495 【模板】中国剩余定理(CRT)/曹冲养猪 2. 洛谷 P3868 [TJOI2009]猜数字 3. POJ 1006 Biorhythms 实践思考与扩展 摘要 (Abstract)
中国剩余定理 (Chinese Remainder Theorem, CRT) 是数论中的一个核心定理,用于求解一组线性同余方程。当模数两两互质时,CRT 提供了一种高效的构造性方法,能够找到满足所有方程的唯一解(在模所有模数之积的意义下)。该定理不仅在纯数学领域有重要意义,在计算机科学、密码学和工程领域也有着广泛应用。
背景与动机 (Motivation)
中国剩余定理最早见于中国南北朝时期的数学著作《孙子算经》中,被称为“孙子问题”或“物不知数”问题。原文如下:
“今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?”
这个问题用现代数学语言描述,就是求解一个线性同余方程组。CRT 不仅是一个古老的智力谜题,它在现代技术中也扮演着关键角色。例如,在密码学(如 RSA 算法)中,CRT 可以加速模幂运算;在计算机科学中,它可以用于处理大数运算,通过将一个大数上的计算分解到多个较小的模数上并行处理,然后用 CRT 合并结果。
问题定义 (Problem Definition)
给定一组 n n n 个线性同余方程:
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) ⋮ x ≡ a n ( m o d m n )
\begin{cases}
x \equiv a_1 \pmod{m_1} \\
x \equiv a_2 \pmod{m_2} \\
\vdots \\
x \equiv a_n \pmod{m_n}
\end{cases}
⎩ ⎨ ⎧ x ≡ a 1 ( mod m 1 ) x ≡ a 2 ( mod m 2 ) ⋮ x ≡ a n ( mod m n )
其中 m 1 , m 2 , … , m n m_1, m_2, \dots, m_n m 1 , m 2 , … , m n 是两两互质的正整数。我们的目标是找到一个整数 x x x ,满足所有这些方程。
一句话算法
模数累乘为总模,部分模积求逆元,各项累加构造解。
这句口诀概括了 CRT 的核心构造步骤:首先计算所有模数的乘积 M M M ,然后对每个方程,计算 M M M 除以当前模数 m i m_i m i 的“部分模积” M i M_i M i ,并求出 M i M_i M i 在模 m i m_i m i 下的逆元,最后将各项结果加权相加,得到最终解。
关键思路 (Key Idea)
CRT 的精髓在于“分解与合并”。我们试图将求解一个复杂的联立方程组问题,分解为 n n n 个独立的小问题,然后将这些小问题的解巧妙地组合起来。
具体来说,我们的目标是构造一个解 x x x 。我们可以尝试构造 n n n 个特殊的数 e 1 , e 2 , … , e n e_1, e_2, \dots, e_n e 1 , e 2 , … , e n ,使得:
e i ≡ 1 ( m o d m i ) e_i \equiv 1 \pmod{m_i} e i ≡ 1 ( mod m i )
e i ≡ 0 ( m o d m j ) e_i \equiv 0 \pmod{m_j} e i ≡ 0 ( mod m j ) (对于所有 j ≠ i j \neq i j = i )
如果能找到这样的 e i e_i e i ,那么最终的解就可以被构造为:
x = a 1 e 1 + a 2 e 2 + ⋯ + a n e n x = a_1 e_1 + a_2 e_2 + \dots + a_n e_n x = a 1 e 1 + a 2 e 2 + ⋯ + a n e n
为什么这个构造是正确的?当我们验证 x ( m o d m i ) x \pmod{m_i} x ( mod m i ) 时,所有 j ≠ i j \neq i j = i 的项 a j e j a_j e_j a j e j 都变成了 0 0 0 ,只剩下 a i e i a_i e_i a i e i 。因为 e i ≡ 1 ( m o d m i ) e_i \equiv 1 \pmod{m_i} e i ≡ 1 ( mod m i ) ,所以 a i e i ≡ a i ( m o d m i ) a_i e_i \equiv a_i \pmod{m_i} a i e i ≡ a i ( mod m i ) 。这样,构造出的 x x x 就满足了第 i i i 个方程。由于这对所有 i i i 都成立,所以它就是整个方程组的解。
那么,如何构造 e i e_i e i 呢?
为了满足 e i ≡ 0 ( m o d m j ) e_i \equiv 0 \pmod{m_j} e i ≡ 0 ( mod m j ) (对于 j ≠ i j \neq i j = i ), e i e_i e i 必须是所有 m j m_j m j 的公倍数。最简单的方法是让 e i e_i e i 包含 ∏ j ≠ i m j \prod_{j \neq i} m_j ∏ j = i m j 这个因子。我们令 M i = ∏ j ≠ i m j = ( ∏ k = 1 n m k ) / m i M_i = \prod_{j \neq i} m_j = (\prod_{k=1}^n m_k) / m_i M i = ∏ j = i m j = ( ∏ k = 1 n m k ) / m i 。
现在我们需要满足 e i ≡ 1 ( m o d m i ) e_i \equiv 1 \pmod{m_i} e i ≡ 1 ( mod m i ) 。我们已经有了 M i M_i M i ,需要再乘以一个数 t i t_i t i ,使得 M i ⋅ t i ≡ 1 ( m o d m i ) M_i \cdot t_i \equiv 1 \pmod{m_i} M i ⋅ t i ≡ 1 ( mod m i ) 。这个 t i t_i t i 正是 M i M_i M i 在模 m i m_i m i 意义下的乘法逆元。
因此, e i = M i ⋅ ( M i − 1 ( m o d m i ) ) e_i = M_i \cdot (M_i^{-1} \pmod{m_i}) e i = M i ⋅ ( M i − 1 ( mod m i )) 。最终解就是 x = ∑ i = 1 n a i M i t i x = \sum_{i=1}^n a_i M_i t_i x = ∑ i = 1 n a i M i t i 。
算法步骤 (Algorithm Steps)
计算总模 :计算所有模数的乘积 M = ∏ i = 1 n m i M = \prod_{i=1}^n m_i M = ∏ i = 1 n m i 。
遍历每个方程 : 对于第 i = 1 , … , n i = 1, \dots, n i = 1 , … , n 个方程:
a. 计算部分模积 : M i = M / m i M_i = M / m_i M i = M / m i 。
b. 求解逆元 : 使用扩展欧几里得算法 (Extended Euclidean Algorithm) 求解 M i M_i M i 在模 m i m_i m i 下的乘法逆元 t i t_i t i ,使得 M i ⋅ t i ≡ 1 ( m o d m i ) M_i \cdot t_i \equiv 1 \pmod{m_i} M i ⋅ t i ≡ 1 ( mod m i ) 。
构造解 : 根据公式 x = ∑ i = 1 n a i M i t i x = \sum_{i=1}^n a_i M_i t_i x = ∑ i = 1 n a i M i t i 计算出一个特解。
求最小正整数解 : 最终解是 x ( m o d M ) x \pmod M x ( mod M ) 。为了确保是最小正整数解,可以写成 ( x ( m o d M ) + M ) ( m o d M ) (x \pmod M + M) \pmod M ( x ( mod M ) + M ) ( mod M ) 。
算法证明
我们来证明构造出的解 x 0 = ∑ j = 1 n a j M j t j x_0 = \sum_{j=1}^n a_j M_j t_j x 0 = ∑ j = 1 n a j M j t j 是方程组的一个解。
我们需要验证对于任意 k ∈ { 1 , … , n } k \in \{1, \dots, n\} k ∈ { 1 , … , n } ,都有 x 0 ≡ a k ( m o d m k ) x_0 \equiv a_k \pmod{m_k} x 0 ≡ a k ( mod m k ) 。
x 0 ( m o d m k ) = ( ∑ j = 1 n a j M j t j ) ( m o d m k )
x_0 \pmod{m_k} = \left( \sum_{j=1}^n a_j M_j t_j \right) \pmod{m_k}
x 0 ( mod m k ) = ( j = 1 ∑ n a j M j t j ) ( mod m k ) 我们将求和拆开:
当 j ≠ k j \neq k j = k 时, M j = M / m j M_j = M/m_j M j = M / m j 的定义中包含了因子 m k m_k m k (因为模数两两互质)。因此,M j ≡ 0 ( m o d m k ) M_j \equiv 0 \pmod{m_k} M j ≡ 0 ( mod m k ) 。所以 a j M j t j ≡ 0 ( m o d m k ) a_j M_j t_j \equiv 0 \pmod{m_k} a j M j t j ≡ 0 ( mod m k ) 。
当 j = k j = k j = k 时,该项为 a k M k t k a_k M_k t_k a k M k t k 。根据 t k t_k t k 的定义,我们有 M k t k ≡ 1 ( m o d m k ) M_k t_k \equiv 1 \pmod{m_k} M k t k ≡ 1 ( mod m k ) 。
因此,原求和式在模 m k m_k m k 意义下,只有第 k k k 项不为零:
x 0 ≡ 0 + ⋯ + 0 + a k ( M k t k ) + 0 + ⋯ + 0 ( m o d m k )
x_0 \equiv 0 + \dots + 0 + a_k (M_k t_k) + 0 + \dots + 0 \pmod{m_k}
x 0 ≡ 0 + ⋯ + 0 + a k ( M k t k ) + 0 + ⋯ + 0 ( mod m k )
x 0 ≡ a k ⋅ 1 ( m o d m k )
x_0 \equiv a_k \cdot 1 \pmod{m_k}
x 0 ≡ a k ⋅ 1 ( mod m k )
x 0 ≡ a k ( m o d m k )
x_0 \equiv a_k \pmod{m_k}
x 0 ≡ a k ( mod m k )
此式对所有 k = 1 , … , n k=1, \dots, n k = 1 , … , n 均成立,故 x 0 x_0 x 0 是方程组的一个解。所有通解可以表示为 x 0 + k M x_0 + kM x 0 + k M (k k k 为任意整数),最小非负整数解即为 x 0 ( m o d M ) x_0 \pmod M x 0 ( mod M ) 。
复杂度分析
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
#include < iostream>
#include < vector>
using ll = long long;
ll extended_gcd ( ll a, ll b, ll & x, ll & y) {
if ( a == 0 ) {
x = 0 ;
y = 1 ;
return b;
}
ll x1, y1;
ll gcd = extended_gcd ( b % a, a, x1, y1) ;
x = y1 - ( b / a) * x1;
y = x1;
return gcd;
}
ll mod_inverse ( ll a, ll m) {
ll x, y;
ll g = extended_gcd ( a, m, x, y) ;
if ( g != 1 ) {
return - 1 ;
}
return ( x % m + m) % m;
}
ll chinese_remainder_theorem ( const std: : vector< ll> & a, const std: : vector< ll> & m) {
int n = a. size ( ) ;
ll M = 1 ;
for ( int i = 0 ; i < n; ++ i) {
M * = m[ i] ;
}
ll result = 0 ;
for ( int i = 0 ; i < n; ++ i) {
ll Mi = M / m[ i] ;
ll ti = mod_inverse ( Mi, m[ i] ) ;
result = ( result + a[ i] * Mi * ti) % M;
}
return ( result + M) % M;
}
int main ( ) {
std: : vector< ll> a = { 2 , 3 , 2 } ;
std: : vector< ll> m = { 3 , 5 , 7 } ;
ll solution = chinese_remainder_theorem ( a, m) ;
std: : cout < < "方程组的解是: " < < solution < < std: : endl;
return 0 ;
}
测试用例
我们使用《孙子算经》中的经典问题作为测试用例。
问题 :
{ x ≡ 2 ( m o d 3 ) x ≡ 3 ( m o d 5 ) x ≡ 2 ( m o d 7 )
\begin{cases}
x \equiv 2 \pmod 3 \\
x \equiv 3 \pmod 5 \\
x \equiv 2 \pmod 7
\end{cases}
⎩ ⎨ ⎧ x ≡ 2 ( mod 3 ) x ≡ 3 ( mod 5 ) x ≡ 2 ( mod 7 )
手动演算 :
模数 : m 1 = 3 , m 2 = 5 , m 3 = 7 m_1=3, m_2=5, m_3=7 m 1 = 3 , m 2 = 5 , m 3 = 7 。
总模 : M = 3 × 5 × 7 = 105 M = 3 \times 5 \times 7 = 105 M = 3 × 5 × 7 = 105 。
计算各项 :
i = 1 :
a 1 = 2 , m 1 = 3 a_1=2, m_1=3 a 1 = 2 , m 1 = 3
M 1 = M / m 1 = 105 / 3 = 35 M_1 = M/m_1 = 105/3 = 35 M 1 = M / m 1 = 105/3 = 35
求 35 ⋅ t 1 ≡ 1 ( m o d 3 ) 35 \cdot t_1 \equiv 1 \pmod 3 35 ⋅ t 1 ≡ 1 ( mod 3 ) 。因为 35 ≡ 2 ( m o d 3 ) 35 \equiv 2 \pmod 3 35 ≡ 2 ( mod 3 ) ,即求 2 ⋅ t 1 ≡ 1 ( m o d 3 ) 2 \cdot t_1 \equiv 1 \pmod 3 2 ⋅ t 1 ≡ 1 ( mod 3 ) 。解得 t 1 = 2 t_1=2 t 1 = 2 。
本项贡献: a 1 M 1 t 1 = 2 × 35 × 2 = 140 a_1 M_1 t_1 = 2 \times 35 \times 2 = 140 a 1 M 1 t 1 = 2 × 35 × 2 = 140 。
i = 2 :
a 2 = 3 , m 2 = 5 a_2=3, m_2=5 a 2 = 3 , m 2 = 5
M 2 = M / m 2 = 105 / 5 = 21 M_2 = M/m_2 = 105/5 = 21 M 2 = M / m 2 = 105/5 = 21
求 21 ⋅ t 2 ≡ 1 ( m o d 5 ) 21 \cdot t_2 \equiv 1 \pmod 5 21 ⋅ t 2 ≡ 1 ( mod 5 ) 。因为 21 ≡ 1 ( m o d 5 ) 21 \equiv 1 \pmod 5 21 ≡ 1 ( mod 5 ) ,即求 1 ⋅ t 2 ≡ 1 ( m o d 5 ) 1 \cdot t_2 \equiv 1 \pmod 5 1 ⋅ t 2 ≡ 1 ( mod 5 ) 。解得 t 2 = 1 t_2=1 t 2 = 1 。
本项贡献: a 2 M 2 t 2 = 3 × 21 × 1 = 63 a_2 M_2 t_2 = 3 \times 21 \times 1 = 63 a 2 M 2 t 2 = 3 × 21 × 1 = 63 。
i = 3 :
a 3 = 2 , m 3 = 7 a_3=2, m_3=7 a 3 = 2 , m 3 = 7
M 3 = M / m 3 = 105 / 7 = 15 M_3 = M/m_3 = 105/7 = 15 M 3 = M / m 3 = 105/7 = 15
求 15 ⋅ t 3 ≡ 1 ( m o d 7 ) 15 \cdot t_3 \equiv 1 \pmod 7 15 ⋅ t 3 ≡ 1 ( mod 7 ) 。因为 15 ≡ 1 ( m o d 7 ) 15 \equiv 1 \pmod 7 15 ≡ 1 ( mod 7 ) ,即求 1 ⋅ t 3 ≡ 1 ( m o d 7 ) 1 \cdot t_3 \equiv 1 \pmod 7 1 ⋅ t 3 ≡ 1 ( mod 7 ) 。解得 t 3 = 1 t_3=1 t 3 = 1 。
本项贡献: a 3 M 3 t 3 = 2 × 15 × 1 = 30 a_3 M_3 t_3 = 2 \times 15 \times 1 = 30 a 3 M 3 t 3 = 2 × 15 × 1 = 30 。
合并解 :
x = 140 + 63 + 30 = 233 x = 140 + 63 + 30 = 233 x = 140 + 63 + 30 = 233 。
求最小正整数解 :
x ( m o d 105 ) = 233 ( m o d 105 ) = 23 x \pmod{105} = 233 \pmod{105} = 23 x ( mod 105 ) = 233 ( mod 105 ) = 23 。
代码运行结果 :
方程组的解是: 23
结果与手动演算一致。
经典例题
1. 洛谷 P1495 【模板】中国剩余定理(CRT)/曹冲养猪
链接 : Luogu P1495
描述 : 这是一个最直接的 CRT 模板题。题目给出一系列 x ≡ a i ( m o d m i ) x \equiv a_i \pmod{m_i} x ≡ a i ( mod m i ) 的方程,其中 m i m_i m i 两两互质,要求求出最小的正整数解 x x x 。
解题思路 : 直接套用上面给出的 CRT 代码模板即可解决。注意数据范围可能需要使用 long long。
2. 洛谷 P3868 [TJOI2009]猜数字
链接 : Luogu P3868
描述 : 题目给出一组数 a i a_i a i ,要求找到一个最小的 x x x 满足对于所有的 i i i ,都有 x ≡ a i ( m o d b i ) x \equiv a_i \pmod{b_i} x ≡ a i ( mod b i ) ,其中 b i b_i b i 是一组给定的数。这道题的模数 b i b_i b i 不一定两两互质。
解题思路 : 这是对 CRT 的扩展,需要使用 扩展中国剩余定理 (EXCRT) 。EXCRT 的思想是逐个合并方程。假设我们已经求出了前 k − 1 k-1 k − 1 个方程的解 x k − 1 x_{k-1} x k − 1 ,通解为 x = x k − 1 + t ⋅ M k − 1 x = x_{k-1} + t \cdot M_{k-1} x = x k − 1 + t ⋅ M k − 1 (其中 M k − 1 M_{k-1} M k − 1 是前 k − 1 k-1 k − 1 个模数的最小公倍数)。现在要满足第 k k k 个方程,即 x k − 1 + t ⋅ M k − 1 ≡ a k ( m o d m k ) x_{k-1} + t \cdot M_{k-1} \equiv a_k \pmod{m_k} x k − 1 + t ⋅ M k − 1 ≡ a k ( mod m k ) 。这是一个关于 t t t 的线性同余方程,可以用扩展欧几里得算法求解。
3. POJ 1006 Biorhythms
链接 : POJ 1006
描述 : 人有体力、情感、智力三个周期,分别为 23, 28, 33 天。给定三个周期的高峰出现的日子 p , e , i p, e, i p , e , i 和一个起始日期 d d d ,要求计算从第 d + 1 d+1 d + 1 天开始,下一次三个周期同时达到高峰是哪一天。
解题思路 : 设下一次高峰在第 x x x 天,则问题可以转化为求解同余方程组:
{ x ≡ p ( m o d 23 ) x ≡ e ( m o d 28 ) x ≡ i ( m o d 33 )
\begin{cases}
x \equiv p \pmod{23} \\
x \equiv e \pmod{28} \\
x \equiv i \pmod{33}
\end{cases}
⎩ ⎨ ⎧ x ≡ p ( mod 23 ) x ≡ e ( mod 28 ) x ≡ i ( mod 33 )
由于 23, 28, 33 两两互质,这是一个标准的 CRT 问题。解出最小的正整数解 x 0 x_0 x 0 后,如果 x 0 ≤ d x_0 \le d x 0 ≤ d ,则需要加上周期的最小公倍数,直到 x > d x > d x > d 。
实践思考与扩展
“扩展:模数不互质的情况”
当模数 m 1 , m 2 , … , m n m_1, m_2, \dots, m_n m 1 , m 2 , … , m n 不满足两两互质时,标准的中国剩余定理不再适用。此时需要使用 扩展中国剩余定理 (Extended Chinese Remainder Theorem, EXCRT) 。
EXCRT 的核心思想是两两合并 。
假设我们要求解两个方程:
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 )
\begin{cases}
x \equiv a_1 \pmod{m_1} \\
x \equiv a_2 \pmod{m_2}
\end{cases}
{ x ≡ a 1 ( mod m 1 ) x ≡ a 2 ( mod m 2 )
由第一个方程,可设 x = a 1 + k ⋅ m 1 x = a_1 + k \cdot m_1 x = a 1 + k ⋅ m 1 。
代入第二个方程,得到 a 1 + k ⋅ m 1 ≡ a 2 ( m o d m 2 ) a_1 + k \cdot m_1 \equiv a_2 \pmod{m_2} a 1 + k ⋅ m 1 ≡ a 2 ( mod m 2 ) 。
整理得 k ⋅ m 1 ≡ a 2 − a 1 ( m o d m 2 ) k \cdot m_1 \equiv a_2 - a_1 \pmod{m_2} k ⋅ m 1 ≡ a 2 − a 1 ( mod m 2 ) 。
这是一个关于 k k k 的线性同余方程,可以使用扩展欧几里得算法求解。
如果方程无解 (当 gcd ( m 1 , m 2 ) \gcd(m_1, m_2) g cd( m 1 , m 2 ) 不能整除 a 2 − a 1 a_2 - a_1 a 2 − a 1 时),则原方程组无解。
否则,解出 k k k 的一个特解 k 0 k_0 k 0 ,则 k k k 的通解为 k = k 0 + t ⋅ m 2 gcd ( m 1 , m 2 ) k = k_0 + t \cdot \frac{m_2}{\gcd(m_1, m_2)} k = k 0 + t ⋅ g c d ( m 1 , m 2 ) m 2 。
将 k k k 的通解代回 x = a 1 + k ⋅ m 1 x = a_1 + k \cdot m_1 x = a 1 + k ⋅ m 1 ,得到 x x x 的新表达式,这个表达式实际上等价于一个新的同余方程 x ≡ a ′ ( m o d M ′ ) x \equiv a' \pmod{M'} x ≡ a ′ ( mod M ′ ) ,其中 M ′ = lcm ( m 1 , m 2 ) M' = \text{lcm}(m_1, m_2) M ′ = lcm ( m 1 , m 2 ) 。
重复此过程,将这个新方程与第三个方程合并,直到所有方程合并完毕。
这种方法更加通用,但实现起来也相对复杂。