13 中国剩余定理 CRT
学习目标
学完这一节,你应该能够:
- 理解中国剩余定理解决什么问题;
- 理解经典 CRT 为什么要求模数两两互质;
- 理解“只对一个同余条件起作用的开关”怎样构造;
- 一步一步推出 CRT 的构造公式;
- 验证构造出的解确实满足所有同余条件;
- 理解为什么解在模所有模数乘积的意义下唯一;
- 看懂经典 CRT 的 C++ 实现。
1. CRT 要解决什么问题
有时一个整数需要同时满足多个余数条件。
例如求整数 x,满足:
x≡2(mod3)
x≡3(mod5)
x≡2(mod7)
也就是说:
- x 除以 3 余 2;
- x 除以 5 余 3;
- x 除以 7 余 2。
枚举可以发现:
x=23
因为:
23mod3=2
23mod5=3
23mod7=2
中国剩余定理就是用来系统解决这类问题的。
2. 经典中国剩余定理的条件
考虑:
x≡a1(modm1)
x≡a2(modm2)
一直到:
x≡ak(modmk)
经典 CRT 要求:
m1,m2,…,mk
两两互质。
“两两互质”表示任意两个不同模数都满足:
gcd(mi,mj)=1
在这个条件下:
- 方程组一定有解;
- 所有解在模:
M=m1m2⋯mk
意义下属于同一个剩余类。
下面从“怎样构造一个解”开始。
3. 先构造一个只负责第一个条件的数
先看三个模数:
m1,m2,m3
希望构造一个数 E1,满足:
E1≡1(modm1)
但是:
E1≡0(modm2)
并且:
E1≡0(modm3)
为什么这样构造有用?
因为把它乘上 a1 后:
模 m1:
a1E1≡a1(modm1)
模 m2:
a1E1≡0(modm2)
模 m3:
a1E1≡0(modm3)
所以:
a1E1 只负责第一个余数条件,不会干扰另外两个条件。
可以把 E1 理解成“只打开第一个条件的开关”。
4. 怎样先让它在其他模数下都等于 0
为了让:
E1≡0(modm2)
以及:
E1≡0(modm3)
先取:
M1=m2m3
因为 M1 含有因子 m2:
m2∣M1
所以:
M1≡0(modm2)
同理:
M1≡0(modm3)
这样,“在其他模数下等于 0”的要求已经满足。
5. 但是 M1 在模 m1 下未必等于 1
我们还希望:
E1≡1(modm1)
但是:
M1=m2m3
模 m1 后一般不一定等于 1。
所以再乘一个整数 t1,希望:
M1t1≡1(modm1)
也就是说,t1 应该是 M1 在模 m1 下的逆元。
6. 为什么 M1 的逆元一定存在
因为:
m1,m2,m3
两两互质。
假设 M1 和 m1 有一个大于 1 的公共质因子 q。
那么:
q∣M1=m2m3
因为 q 是质数,所以 q 必须整除 m2 或 m3 中至少一个。
同时又有:
q∣m1
这就说明 m1 与 m2 或 m3 有大于 1 的公共因子,与“两两互质”矛盾。
所以:
gcd(M1,m1)=1
因此 M1 在模 m1 下存在逆元。
可以找到 t1,使:
M1t1≡1(modm1)
于是令:
E1=M1t1
就有:
E1≡1(modm1)
并且因为 E1 仍然含有因子 M1=m2m3:
E1≡0(modm2)
E1≡0(modm3)
7. 同样构造其他“开关”
令:
M=m1m2m3
定义:
M1=m1M
M2=m2M
M3=m3M
分别寻找:
M1t1≡1(modm1)
M2t2≡1(modm2)
M3t3≡1(modm3)
再令:
E1=M1t1
E2=M2t2
E3=M3t3
那么:
- E1 只在模 m1 下等于 1,在另外两个模数下等于 0;
- E2 只在模 m2 下等于 1,在另外两个模数下等于 0;
- E3 只在模 m3 下等于 1,在另外两个模数下等于 0。
8. 把这些“开关”加起来
构造:
x=a1E1+a2E2+a3E3
模 m1:
x≡a1⋅1+a2⋅0+a3⋅0(modm1)
所以:
x≡a1(modm1)
模 m2:
x≡a1⋅0+a2⋅1+a3⋅0(modm2)
所以:
x≡a2(modm2)
模 m3:
x≡a1⋅0+a2⋅0+a3⋅1(modm3)
所以:
x≡a3(modm3)
因此这个 x 同时满足三个同余条件。
9. 推广到 k 个同余条件
令:
M=m1m2⋯mk
对每个 i 定义:
Mi=miM
因为所有模数两两互质,所以:
gcd(Mi,mi)=1
因此 Mi 在模 mi 下存在逆元。
设 ti 满足:
Miti≡1(modmi)
那么:
Ei=Miti
满足:
Ei≡1(modmi)
对于任意 j=i,因为 Mi 中仍然含有因子 mj,所以:
Ei≡0(modmj)
因此构造:
x=a1M1t1+a2M2t2+⋯+akMktk
就能同时满足全部同余条件。
也可以用求和符号简写为:
x≡i=1∑kaiMiti(modM)
这里的 ∑ 只是表示把从 i=1 到 i=k 的这些项全部加起来。
10. 用具体例子完整构造一次
求:
x≡2(mod3)
x≡3(mod5)
x≡2(mod7)
三个模数:
3,5,7
两两互质。
先算:
M=3×5×7=105
10.1 第一个条件
M1=3105=35
寻找 t1,使:
35t1≡1(mod3)
因为:
35≡2(mod3)
所以:
2t1≡1(mod3)
取:
t1=2
因为:
2×2=4≡1(mod3)
所以:
E1=35×2=70
10.2 第二个条件
M2=5105=21
寻找 t2,使:
21t2≡1(mod5)
因为:
21≡1(mod5)
所以:
t2=1
因此:
E2=21
10.3 第三个条件
M3=7105=15
寻找 t3,使:
15t3≡1(mod7)
因为:
15≡1(mod7)
所以:
t3=1
因此:
E3=15
10.4 合并
构造:
x=2E1+3E2+2E3
代入:
x=2×70+3×21+2×15
所以:
x=140+63+30=233
而:
233=105×2+23
所以:
233≡23(mod105)
最小非负解是:
x=23
11. 为什么解在模 M 下唯一
假设 x1 和 x2 都满足所有同余条件。
那么对于每个 i:
x1≡ai(modmi)
并且:
x2≡ai(modmi)
所以:
x1≡x2(modmi)
根据同余的定义:
mi∣(x1−x2)
也就是说,每一个:
m1,m2,…,mk
都整除:
x1−x2
接下来需要说明:为什么它们的乘积也整除这个差。
12. 一个需要用到的小结论
如果:
gcd(u,v)=1
并且:
u∣N
v∣N
那么:
uv∣N
证明如下。
因为:
u∣N
所以存在整数 s,使:
N=us
又因为:
v∣N
所以:
v∣us
由:
gcd(u,v)=1
根据裴蜀定理,存在整数 A,B,使:
Au+Bv=1
两边乘 s:
Aus+Bvs=s
因为:
v∣us
所以:
v∣Aus
而:
v∣Bvs
因此:
v∣s
所以存在整数 t,使:
s=vt
于是:
N=us=uvt
因此:
uv∣N
小结论得证。
13. 用这个结论证明 CRT 的唯一性
因为:
m1,m2,…,mk
两两互质,而且每个 mi 都整除:
x1−x2
所以先由:
m1∣(x1−x2)
和:
m2∣(x1−x2)
再利用上一小节的小结论,得到:
m1m2∣(x1−x2)
接下来还需要确认:
gcd(m1m2,m3)=1
为什么?
如果 m1m2 和 m3 有一个公共质因子 q,那么 q 整除 m1m2,所以 q 至少整除 m1,m2 中的一个。
同时 q 又整除 m3。
这就会导致 m3 与 m1 或 m2 不互质,与“两两互质”矛盾。
所以:
gcd(m1m2,m3)=1
于是再利用上一小节的小结论:
m1m2m3∣(x1−x2)
继续同样的过程,每加入一个新的 mi,它都与前面已经相乘得到的模数乘积互质。
因此最终:
m1m2⋯mk∣(x1−x2)
也就是:
M∣(x1−x2)
根据同余的定义:
x1≡x2(modM)
所以:
所有解在模 M 的意义下属于同一个剩余类。
这就是“解在模 M 下唯一”的含义。
14. C++ 中怎样求 ti
ti 是 Mi 在模 mi 下的逆元。
前面已经学过,可以使用扩展欧几里得算法求逆元:
long long exgcd(long long a, long long b,
long long &x, long long &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
long long x1, y1;
long long g = exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
return g;
}
long long inv(long long a, long long mod) {
long long x, y;
exgcd(a, mod, x, y);
return (x % mod + mod) % mod;
}
15. 经典 CRT 模板
下面模板假设所有模数的乘积 M 能够放进 long long。
long long CRT(const vector<long long>& a,
const vector<long long>& m) {
long long M = 1;
for (long long x : m) {
M *= x;
}
long long ans = 0;
for (int i = 0; i < (int)a.size(); i++) {
long long Mi = M / m[i];
long long ti = inv(Mi, m[i]);
long long ai = (a[i] % M + M) % M;
long long add =
(long long)((__int128)ai * Mi % M * ti % M);
ans = (long long)(((__int128)ans + add) % M);
}
return ans;
}
这里使用 __int128 计算中间乘积,是为了降低 a[i] * Mi * ti 在乘法过程中溢出的风险。
如果连所有模数的乘积 M 本身都超过 long long,还需要进一步改写数据类型和算法实现。
16. 代码逐步对应公式
先计算:
long long M = 1;
for (long long x : m) {
M *= x;
}
对应:
M=m1m2⋯mk
然后:
long long Mi = M / m[i];
对应:
Mi=miM
接着:
long long ti = inv(Mi, m[i]);
对应:
Miti≡1(modmi)
最后:
ans = ans + a[i] * Mi * ti;
在数学上对应不断加入:
aiMiti
最终得到:
x≡i=1∑kaiMiti(modM)
17. 为什么经典 CRT 强调“两两互质”
构造中的关键一步是:
Mi=miM
必须在模 mi 下存在逆元。
这要求:
gcd(Mi,mi)=1
当所有模数两两互质时,这个条件自动成立。
如果模数不两两互质,就不能直接使用这里的经典 CRT 构造。
那种情况需要另外判断各个同余条件是否相容,并使用更一般的方法处理,不属于本章的经典 CRT 范围。
18. 小练习
练习 1
为什么希望构造:
Ei≡1(modmi)
同时对于 j=i:
Ei≡0(modmj)
练习 2
为什么:
Mi=miM
在模其他 mj 下自动等于 0?
练习 3
为什么 Mi 在模 mi 下存在逆元?
练习 4
求最小非负整数 x:
x≡1(mod3)
x≡2(mod5)
19. 答案
练习 1
因为这样 aiEi 只负责第 i 个余数条件,不会影响其他条件。
练习 2
当 j=i 时,Mi 的乘积中仍然包含因子 mj,所以:
mj∣Mi
因此:
Mi≡0(modmj)
练习 3
因为所有模数两两互质,所以:
gcd(Mi,mi)=1
根据逆元存在条件,Mi 在模 mi 下存在逆元。
练习 4
满足:
x≡1(mod3)
的数有:
1,4,7,10,…
其中:
7≡2(mod5)
所以最小非负解是:
7
有帮助,赞一个