CSP-S 提高级课程中心 / 数学进阶 / 第47讲
CSP-S 提高级 · 数学进阶 · s6-06

中国剩余定理

"三三数之剩二,五五数之剩三,七七数之剩二" —— 一句一千五百年前的数学谜题,藏着一个把多个同余条件"拼装"成一个答案的漂亮构造。模数两两互质时它叫 CRT,不互质时它叫 exCRT。

6 大模块 O(n log m) CRT 3 个案例 CRT + 扩展 CRT 12 道洛谷原题
本讲 · 第 47/52 讲中国剩余定理
模块 01

情景导航

从一个一千五百年前的数学谜题出发,看古人如何"不知道总数,却算出了总数"。

故事韩信点兵与《孙子算经》

传说淮阴侯韩信点兵,从不一个一个数。他让士兵先排成 3 人一排,看最后余下几个;再排成 5 人一排,看余下几个;最后排成 7 人一排,再看余下几个 —— 三次报数报完,他就能说出总人数。

这个方法的原始出处,是大约成书于公元 4 至 5 世纪的《孙子算经》。书里有一道著名的题:

今有物不知其数,
三三数之剩二,五五数之剩三,七七数之剩二,
问物几何?

翻译成现代语言:有一堆东西,不知道有多少个。三个三个地数,最后剩 2 个;五个五个地数,最后剩 3 个;七个七个地数,最后剩 2 个。问这堆东西最少有多少个?

用同余式写出来,就是三个方程同时成立:

x ≡ 2 (mod 3)   x ≡ 3 (mod 5)   x ≡ 2 (mod 7)

《孙子算经》给出的答案是 23。书中还留下了一首口诀:"三人同行七十稀,五树梅花廿一枝,七子团圆正半月,除百零五便得知。"

口诀里的 70、21、15 三个数,正对应着后面要学的"构造法"中的 aᵢ · Mᵢ · tᵢ 三项:

2 × 70 + 3 × 21 + 2 × 15 = 233  →  233 − 210 = 23

而"除百零五便得知"里的 105,就是 3 × 5 × 7 —— 也就是答案的周期。整套思路,就是今天要讲的中国剩余定理。

💡 为什么这个问题这么重要

中国剩余定理(Chinese Remainder Theorem,简称 CRT)是数论中少数以中国命名的定理之一。它解决的是"多个同余条件同时成立"这一基本问题,而这类问题在竞赛中随处可见:

① 大模数取模:当模数是一个很大的合数时,把它分解成几个互质的因子,分别在每个小模数下计算,最后用 CRT 合并回来(典型案例:P2480 古代猪文把 999911658 拆成 2 × 3 × 4679 × 35617);

② 任意模数多项式乘法(MTT):用三个 NTT 模数分别做卷积,再用 CRT 合并结果,从而绕过单个模数的精度限制;

③ 模数不互质的场景:扩展中国剩余定理(exCRT)用"两个两个合并"的思路,把互质条件彻底去掉;

④ 算法竞赛中的计数问题:答案需要分别对若干模数取模后再拼装输出。

更重要的是,CRT 的证明过程本身就是一次 exgcd 的绝佳应用 —— 上一讲的工具在本讲里被反复调用,两讲内容由此串成了一条线。

①生活类比
🏮

排队报数 = 取余

按 3 人一排、5 人一排、7 人一排分别报数,留下的余数是同一个人数的三种"影子"。

→ x mod mᵢ = aᵢ
🧩

拼图 = 构造法

每个同余条件贡献一块"拼图",块与块之间互不干扰(因为模数互质),拼起来就是完整答案。

→ 求和 aᵢMᵢtᵢ
🎡

齿轮咬合 = 周期

三个齿轮齿数互质时,只有转过最小公倍数那么多齿,才会回到同一种啮合状态。

→ 周期 = ∏mᵢ
🔗

两两合并 = exCRT

齿轮齿数不互质时不能一次拼装,但可以先拼两个、再拿结果去拼第三个。

→ x ≡ r (mod lcm)
②核心定义

同余方程组:形如 x ≡ a₁ (mod m₁)、x ≡ a₂ (mod m₂)、…、x ≡ aₙ (mod mₙ) 的一组条件。求同时满足全部条件的整数 x,就是解同余方程组。

中国剩余定理(CRT):若模数 m₁, m₂, …, mₙ 两两互质,则同余方程组在模 M = m₁m₂…mₙ 下有唯一解。记 Mᵢ = M / mᵢ,tᵢ = Mᵢ⁻¹ (mod mᵢ),则解为:

x ≡ Σ aᵢ · Mᵢ · tᵢ  (mod M)

扩展中国剩余定理(exCRT):不要求模数两两互质。核心操作是"合并两个方程":已知 x ≡ r₁ (mod m₁) 与 x ≡ r₂ (mod m₂),设 g = gcd(m₁, m₂),若 g ∤ (r₂ − r₁) 则方程组无解;否则合并为 x ≡ r (mod lcm(m₁, m₂))。重复 n − 1 次即可合并全部方程。

关键差别:CRT 的解法是"一次构造",要求互质;exCRT 的解法是"逐次合并",不要求互质但能判无解。二者在模数互质时结果一致。

📌 本讲核心知识地图

板块核心内容对应大纲
同余方程组多条件同时成立的整数解【7】中国剩余定理
物不知数x ≡ 2(3)、3(5)、2(7) → x = 23【7】中国剩余定理
CRT 构造M = ∏mᵢ,Mᵢ = M/mᵢ,tᵢ = Mᵢ⁻¹ mod mᵢ【7】中国剩余定理
解公式x ≡ Σ aᵢMᵢtᵢ (mod M)【7】中国剩余定理
唯一性模 M 下解唯一,周期为 M【7】中国剩余定理
exCRT 合并m₁t ≡ r₂ − r₁ (mod m₂),判 g | (r₂−r₁)【7】中国剩余定理
新模数lcm(m₁, m₂) = m₁m₂ / g【7】中国剩余定理
典型应用大模数拆分、MTT、曹冲养猪、屠龙勇士【7】中国剩余定理
③核心口诀
🔑 中国剩余定理

模数两两互质,乘积就是周期;
每个条件配一个"Mᵢ 的逆元",三项加权求和再对 M 取模。

🔑 古人口诀

三人同行七十稀(2×70),五树梅花廿一枝(3×21),
七子团圆正半月(2×15),除百零五便得知(233 − 210 = 23)。

🔑 扩展 CRT

模数不互质,两个两个合并;
先写 x = r₁ + m₁t,代入第二式,用 exgcd 解出 t。

🔑 判无解

合并前先算 g = gcd(m₁, m₂),若 g ∤ (r₂ − r₁) 则整个方程组无解;
这一步是 exCRT 与 CRT 最大的区别。

模块 02

知识探究

先看清同余方程组的"影子"本质,再拆开 CRT 的构造法,最后用 exCRT 把互质条件彻底去掉。

1同余方程组:把"影子"还原成"本体" 基础

同余条件 x ≡ a (mod m) 给出的信息是"x 除以 m 余 a",它描述的是 x 的一个"影子" —— 只知道余数,不知道商。单独看一个条件,符合的数有无穷多个;但把几个条件叠在一起,往往就能把 x 钉死。

以物不知数为例,我们分别列出每个条件下允许的 x:

条件① x ≡ 2 (mod 3):x ∈ {2, 5, 8, 11, 14, 17, 20, 23, 26, …}
条件② x ≡ 3 (mod 5):x ∈ {3, 8, 13, 18, 23, 28, 33, …}
条件③ x ≡ 2 (mod 7):x ∈ {2, 9, 16, 23, 30, 37, …}
三个集合的公共元素:23, 128, 233, 338, …(间隔恒为 105 = 3×5×7)

最小正解是 23;所有解构成公差为 105 的等差数列。这说明解的周期是各模数之积 —— 这一点在模数互质时永远成立,也是 CRT 中"M = ∏mᵢ"的由来。

同余方程组的解:一条周期为 M = 105 的等差链 0105210 315420525 23128233 338443548 间隔恒为 105 验证:23 mod 3 = 2,23 mod 5 = 3,23 mod 7 = 2 ✓

什么时候无解?如果两个条件本身互相矛盾,方程组就无解。最简单的例子:x ≡ 1 (mod 4) 与 x ≡ 2 (mod 4)。这一类"模数相同但余数不同"的矛盾最直观;更隐蔽的矛盾出现在模数不互质时,例如 x ≡ 1 (mod 4) 与 x ≡ 2 (mod 6) —— 前者要求 x 是奇数,后者要求 x 是偶数,不可能同时成立。用 exCRT 的判据可以自动检测:g = gcd(4, 6) = 2,而 2 − 1 = 1 不被 2 整除,无解。

2中国剩余定理:三步构造法 核心

设模数 m₁, m₂, …, mₙ 两两互质,要解 x ≡ aᵢ (mod mᵢ)。构造分三步:

1
✖️
算总模数

M = m₁ × m₂ × … × mₙ。它就是最终答案的周期。

2
➗
算每个 Mᵢ 与逆元

Mᵢ = M / mᵢ,再用 exgcd 求 tᵢ = Mᵢ⁻¹ (mod mᵢ),即 Mᵢ·tᵢ ≡ 1 (mod mᵢ)。

3
➕
加权求和取模

x ≡ Σ aᵢ·Mᵢ·tᵢ (mod M)。求和后对 M 取模,得到最小非负解。

为什么每项只"管"自己那一个条件?关键在于 Mᵢ 的性质:当 j ≠ i 时,mⱼ | Mᵢ(因为 Mᵢ 里含有 mⱼ 这个因子),所以 Mᵢ ≡ 0 (mod mⱼ)。这意味着第 i 项 aᵢMᵢtᵢ 对其它所有模数都贡献 0,只在自己那个模数下等于 aᵢ —— 就像拼图上的一块,只在它自己的位置上露出图案。

🎬 交互演示:用 CRT 构造法解"物不知数"(x ≡ 2 mod 3,x ≡ 3 mod 5,x ≡ 2 mod 7)
1第一步:算总模数。M = 3 × 5 × 7 = 105。三个模数两两互质,所以 105 就是答案的周期。
2第二步:算三个 Mᵢ。M₁ = 105/3 = 35、M₂ = 105/5 = 21、M₃ = 105/7 = 15。注意 M₁ = 35 能被 5 和 7 整除,唯独不能被 3 整除。
3第三步:求三个逆元 tᵢ。t₁ 要满足 35 t ≡ 1 (mod 3),即 2t ≡ 1 (mod 3),得 t₁ = 2(2×2 = 4 ≡ 1)。
4继续求逆元。t₂ 要满足 21 t ≡ 1 (mod 5),即 1·t ≡ 1 (mod 5),得 t₂ = 1。t₃ 要满足 15 t ≡ 1 (mod 7),即 1·t ≡ 1 (mod 7),得 t₃ = 1。
5第四步:算三项。第 1 项 = a₁M₁t₁ = 2 × 35 × 2 = 140;第 2 项 = a₂M₂t₂ = 3 × 21 × 1 = 63;第 3 项 = a₃M₃t₃ = 2 × 15 × 1 = 30。这与古人口诀中的 70、21、15 完全对应(古人给出的是"单项贡献值",我们这里多了 aᵢ 因子)。
6验证"只管自己"。看第 1 项 140:140 mod 3 = 2(正好是 a₁),140 mod 5 = 0,140 mod 7 = 0。完美!第 2 项 63:mod 5 = 3 = a₂,mod 3 = 0,mod 7 = 0。第 3 项 30:mod 7 = 2 = a₃,mod 3 = 0,mod 5 = 0。
7第五步:求和取模。x = 140 + 63 + 30 = 233,再对 M = 105 取模:233 mod 105 = 23。所以最小非负解是 23。
8最终验证。23 mod 3 = 2 ✓、23 mod 5 = 3 ✓、23 mod 7 = 2 ✓。全部条件满足。而全部解就是 x ≡ 23 (mod 105)。
💡 三步构造法的精髓在第二步:用 exgcd 求逆元。因为模数两两互质,所以 gcd(Mᵢ, mᵢ) = 1 自动成立,逆元一定存在 —— 这正是"互质"这个前提的全部作用。
3构造出来的一定对吗?解唯一吗? 初赛常考

构造法看着巧妙,但必须回答两个问题:① 这个 x 真的满足所有条件吗?② 只有它满足吗?

问题①:正确性。任取一个 i,验证 x ≡ aᵢ (mod mᵢ)。

把 x = Σⱼ aⱼMⱼtⱼ 拆开看。对于 j ≠ i 的项,因为 mᵢ | Mⱼ(Mⱼ = M/mⱼ 里含有因子 mᵢ,由互质性保证 mᵢ 不会被约掉),所以 Mⱼ ≡ 0 (mod mᵢ),这些项全部归零。

只剩下第 i 项:aᵢMᵢtᵢ ≡ aᵢ · 1 = aᵢ (mod mᵢ),因为 tᵢ 是 Mᵢ 的逆元(Mᵢtᵢ ≡ 1)。

所以对每个 i 都有 x ≡ aᵢ (mod mᵢ),构造出的 x 确实满足全部条件。∎

问题②:唯一性(模 M 意义下)。设 x₁、x₂ 都满足所有条件,则对每个 i 有 x₁ ≡ x₂ (mod mᵢ),即 mᵢ | (x₁ − x₂)。

因为 m₁, …, mₙ 两两互质,它们的最小公倍数就等于乘积 M。既然每个 mᵢ 都整除 (x₁ − x₂),那么它们的乘积也整除 (x₁ − x₂):M | (x₁ − x₂)。

所以 x₁ ≡ x₂ (mod M),即模 M 下解唯一。∎

唯一性为什么必须依赖"两两互质"?因为如果 m₁、m₂ 不互质,"每个 mᵢ 整除 N" 就不能推出 "m₁m₂ 整除 N"。例如 4 | N 且 6 | N 只说明 12 | N(即 lcm(4,6) = 12),而 4×6 = 24 并不整除 N。所以模数不互质时,真实周期是 lcm 而不是乘积 —— 这就是 exCRT 要用最小公倍数的原因。

情形解的情况解的周期解法
模数两两互质一定有解,模 M 下唯一M = ∏ mᵢCRT 一次构造
模数不互质,条件相容有解,模 lcm 下唯一lcm(m₁, …, mₙ)exCRT 逐次合并
模数不互质,条件矛盾无解—exCRT 合并时判定
4CRT 的 C++ 模板 必背模板

题目通常给出 n 组 (aᵢ, mᵢ),要求输出最小非负解。注意数据范围不同,溢出处理方式也不同。

C++CRT 模板(m₁..mₙ 两两互质,答案保证在 long long 内)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;                  // 中间乘法用它防溢出

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}

int main() {
    int n; cin >> n;
    vector<ll> a(n), m(n);
    for (int i = 0; i < n; i++) cin >> a[i] >> m[i];

    ll M = 1;
    for (int i = 0; i < n; i++) M *= m[i];   // 总模数(注意是否会溢出)

    i128 ans = 0;
    for (int i = 0; i < n; i++) {
        ll Mi = M / m[i];                       // 除掉自己的那个模数
        ll ti, y;
        exgcd(Mi % m[i], m[i], ti, y);          // 求 Mi 在 mod m[i] 下的逆元
        ti = (ti % m[i] + m[i]) % m[i];
        ans = (ans + (i128)(a[i] % m[i]) * Mi % M * ti) % M;
    }
    cout << (ll)(ans % M) << endl;
    return 0;
}
▶ 运行结果
输入:
3
2 3
3 5
2 7

输出: 23

输入:
3
1 3
2 5
3 7

输出: 52
验算: 52 mod 3 = 1 ✓,52 mod 5 = 2 ✓,52 mod 7 = 3 ✓

说明: 52 = 1*70 + 2*21 + 3*15 - 105 = 70+42+45-105 = 52 ✓
     (其中 70 = M1*M1的逆元 = 35*2,21 = 21*1,15 = 15*1)

四个实现要点:

① ans 用 __int128 累加,因为 aᵢ · Mᵢ · tᵢ 可能达到 M² 的量级,long long 会溢出;

② 求逆元时写成 exgcd(Mi % m[i], m[i], ti, y),先把 Mi 对 mᵢ 取模,避免 exgcd 内部处理超大参数;

③ 每次加法后立刻 % M,防止累加过程中数值膨胀;

④ 最后输出时要把 __int128 转回 long long,且用 (ans % M + M) % M 保证非负(本题构造法得到的肯定是非负的,但保险起见)。

什么时候不能用这套模板?当 ∏mᵢ 超出 long long 范围(比如 n = 10 且每个 mᵢ 接近 10⁹),需要改用"边合并边取模"的 exCRT 思路 —— 见下一个板块。

5扩展中国剩余定理:不互质时的两两合并 进阶

当模数不互质时,CRT 的构造法会失效(Mᵢ 的逆元可能不存在)。这时改用"合并两个方程"的思路。

已知:x ≡ r₁ (mod m₁) 且 x ≡ r₂ (mod m₂)。目标:合并成 x ≡ r (mod M′)。

第一步:把第一个条件写成等式。既然 x 除以 m₁ 余 r₁,就存在整数 t 使 x = r₁ + m₁·t。

第二步:代入第二个条件。r₁ + m₁·t ≡ r₂ (mod m₂),移项得 m₁·t ≡ r₂ − r₁ (mod m₂)。

第三步:判无解。令 g = gcd(m₁, m₂)。由上一讲的判据,这个同余方程有解 ⟺ g | (r₂ − r₁)。若不能整除,整个方程组无解。

第四步:解出 t。三数同除 g 得 (m₁/g)·t ≡ (r₂−r₁)/g (mod m₂/g),用 exgcd 求逆元解出 t 的最小非负解 t₀。

第五步:写出合并结果。r = r₁ + m₁·t₀,新模数 M′ = lcm(m₁, m₂) = m₁·m₂/g。最终 x ≡ r (mod M′)。

对 n 个方程,从第一个开始,依次把每个新方程"并"进当前结果 —— 一共合并 n − 1 次,就得到最终答案。复杂度 O(n log(max mᵢ))。

STEP 1初始化 r、M
STEP 2取下一个方程
STEP 3算 g 与逆元
STEP 4判 g | (ai − r)
STEP 5更新 r 与 M
STEP 6输出 r mod M
六步循环 n−1 次,互质与否都能处理,且能自动发现无解

动手算一遍:合并 x ≡ 1 (mod 4) 与 x ≡ 2 (mod 6)。

① 写 x = 1 + 4t;② 代入得 4t ≡ 1 (mod 6);③ g = gcd(4, 6) = 2,而 1 不能被 2 整除 → 无解。直观上看:第一个条件要求 x 是奇数,第二个要求 x 是偶数,确实矛盾。

再看一个相容的例子:合并 x ≡ 3 (mod 4) 与 x ≡ 5 (mod 6)。

① x = 3 + 4t;② 4t ≡ 2 (mod 6);③ g = gcd(4,6) = 2,2 | 2 ✓ 有解;④ 同除 2:2t ≡ 1 (mod 3),即 t ≡ 2 (mod 3)(因为 2×2 = 4 ≡ 1),最小非负解 t₀ = 2;⑤ r = 3 + 4×2 = 11,新模数 lcm(4, 6) = 12。答案 x ≡ 11 (mod 12)。

验证:11 mod 4 = 3 ✓,11 mod 6 = 5 ✓。而 11、23、35、47、… 正是同时满足两式的全部整数。

6exCRT 的 C++ 模板 必背模板
C++exCRT 模板(模数不必互质,自动判无解)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}

// 把若干同余方程合并;失败(无解)时返回 false
bool excrt(vector<ll> &a, vector<ll> &m, ll &R, ll &M) {
    R = 0; M = 1;                          // x ≡ 0 (mod 1):单位元,等价于"无约束"
    for (size_t i = 0; i < a.size(); i++) {
        ll A = M, B = m[i], C = ((a[i] - R) % B + B) % B;   // 解 A*t ≡ C (mod B)
        ll x, y;
        ll g = exgcd(A, B, x, y);
        if (C % g) return false;                // 无解
        ll Bg = B / g;
        ll t = (ll)((i128)(C / g) % Bg * ((x % Bg + Bg) % Bg) % Bg);
        // 更新:r = r + M*t,M = lcm(M, B)
        R = (ll)((i128)R + (i128)(M % (M / g * Bg)) * t % (M / g * Bg));
        R = (R % (M / g * Bg) + (M / g * Bg)) % (M / g * Bg);
        M = M / g * Bg;                       // 新模数 = lcm
    }
    R = (R % M + M) % M;
    return true;
}

int main() {
    int n; cin >> n;
    vector<ll> a(n), m(n);
    for (int i = 0; i < n; i++) cin >> m[i] >> a[i];   // 注意读入顺序
    ll R, M;
    if (!excrt(a, m, R, M)) cout << "-1" << endl;
    else cout << R << endl;
    return 0;
}

上面这版为了展示"R 的更新与 lcm 同步"写得很绕,实战中推荐下面这版更简洁的写法(先把新模数算出来,再更新 R):

C++exCRT 模板 · 简化版(推荐)
bool excrt(vector<ll> &a, vector<ll> &m, ll &R, ll &M) {
    R = 0; M = 1;
    for (size_t i = 0; i < a.size(); i++) {
        ll A = M, B = m[i], C = ((a[i] - R) % B + B) % B;
        ll x, y;
        ll g = exgcd(A, B, x, y);              // A*x + B*y = g
        if (C % g) return false;              // g ∤ C → 无解

        ll newM = M / g * B;                   // 新模数 = lcm(M, B),先算好
        // 求 t 使 A*t ≡ C (mod B):t = (C/g) * inv(A/g) mod (B/g)
        ll Bg = B / g;
        ll inv = (x % Bg + Bg) % Bg;           // exgcd 得到的 x 就是 A 模 B 的逆元候选
        ll t = (ll)((i128)(C / g) % Bg * inv % Bg);

        R = (ll)(((i128)R + (i128)M % newM * t) % newM);
        M = newM;
    }
    R = (R % M + M) % M;
    return true;
}
▶ 运行结果
样例1(模数互质,结果应与 CRT 一致)
输入:
3
3 2
5 3
7 2
输出: 23

样例2(两方程相容)
输入:
2
4 3
6 5
输出: 11
验算: 11 mod 4 = 3 ✓,11 mod 6 = 5 ✓

样例3(两方程矛盾)
输入:
2
4 1
6 2
输出: -1
原因: g = gcd(4,6) = 2,但 2 - 1 = 1 不被 2 整除

样例4(三方程且含非互质对)
输入:
3
4 3
6 5
10 7
输出: 47
验算: 47 mod 4 = 3 ✓,47 mod 6 = 5 ✓,47 mod 10 = 7 ✓
说明: 合并前两个得 x ≡ 11 (mod 12),再与 x ≡ 7 (mod 10) 合并,
     g = gcd(12,10) = 2,7 - 11 = -4 被 2 整除,
     解 12t ≡ -4 (mod 10),得 t ≡ 3 (mod 5),
     r = 11 + 12*3 = 47,新模数 lcm(12,10) = 60 → x ≡ 47 (mod 60) ✓

四个实现要点:① 初始值取 R = 0, M = 1(模 1 下所有数同余,相当于"还没有任何约束");② C = ((a[i] - R) % B + B) % B 必须归一化成非负,否则 C 为负时 C % g 的判断会出错;③ 逆元用的是 exgcd 返回的 x,它对应 A·x + B·y = g,所以 x 就是 A 在模 B 下的逆元候选(因为 A·x ≡ g (mod B),但我们需要的是 A/g 在模 B/g 下的逆元,恰好也是 x 归一化后的值);④ 中间乘法全部走 __int128。

案例案例1:P1495 曹冲养猪(CRT 模板题) 模板

题意:输入 n 组数据,第 i 组为 (mᵢ, aᵢ),表示"每次数到 mᵢ 就重新开始,最后剩下 aᵢ 头猪",也就是 x ≡ aᵢ (mod mᵢ)。保证 mᵢ 两两互质,求猪的最少头数(最小非负解)。

分析:这就是 CRT 的标准模板题。原始数据的乘积可能超出 long long,但因为题目保证答案在 long long 内,用 __int128 中转即可。

C++P1495 曹冲养猪 · 完整代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}

int main() {
    int n; cin >> n;
    vector<ll> m(n), a(n);
    for (int i = 0; i < n; i++) cin >> m[i] >> a[i];

    ll M = 1;
    for (int i = 0; i < n; i++) M *= m[i];

    i128 ans = 0;
    for (int i = 0; i < n; i++) {
        ll Mi = M / m[i];
        ll ti, y;
        exgcd(Mi % m[i], m[i], ti, y);           // Mi 在 mod m[i] 下的逆元
        ti = (ti % m[i] + m[i]) % m[i];
        ans = (ans + (i128)(a[i] % m[i]) * Mi % M * ti) % M;
    }
    cout << (ll)ans << endl;
    return 0;
}
▶ 运行结果
输入:
3
3 1
5 2
7 3

输出: 52

逐步展开:
M = 105
i=0: Mi = 35, 35 在 mod 3 下的逆元 = 2    → 1 * 35 * 2 = 70
i=1: Mi = 21, 21 在 mod 5 下的逆元 = 1    → 2 * 21 * 1 = 42
i=2: Mi = 15, 15 在 mod 7 下的逆元 = 1    → 3 * 15 * 1 = 45
求和: 70 + 42 + 45 = 157
取模: 157 mod 105 = 52  ✓

验算: 52 mod 3 = 1 ✓,52 mod 5 = 2 ✓,52 mod 7 = 3 ✓

三个易错点:① M *= m[i] 时若 n 较大可能溢出 long long(本题数据下安全,但养成用 __int128 的习惯);② 求逆元时忘记把 Mi 对 m[i] 取模,exgcd 内部处理大数时容易出问题;③ 累加时没有及时取模,ans 膨胀。

案例案例2:P4777 【模板】扩展中国剩余定理(exCRT) 进阶模板

题意:给定 n 个同余方程 x ≡ bᵢ (mod aᵢ),求最小非负整数解。不保证 aᵢ 两两互质,若方程组无解则需要输出错误信息。

分析:这是 exCRT 的模板题。核心就是"两两合并",每次合并时判断 g | (bᵢ − R)。

C++P4777 exCRT · 完整代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}

int main() {
    int n; cin >> n;
    ll R = 0, M = 1;                 // 当前答案:x ≡ R (mod M)
    for (int i = 0; i < n; i++) {
        ll a, b; cin >> a >> b;      // 新方程:x ≡ b (mod a)
        // 要解 M * t ≡ (b - R) (mod a)
        ll C = ((b - R) % a + a) % a;    // 归一化为非负
        ll x, y;
        ll g = exgcd(M, a, x, y);
        if (C % g) { cout << "-1" << endl; return 0; }   // 无解

        ll newM = M / g * a;             // 新模数 = lcm(M, a)
        ll ag = a / g;
        ll inv = (x % ag + ag) % ag;     // (M/g) 在 mod (a/g) 下的逆元
        ll t = (ll)((i128)(C / g) % ag * inv % ag);
        R = (ll)(((i128)R + (i128)M % newM * t) % newM);
        M = newM;
    }
    R = (R % M + M) % M;                 // 归一到 [0, M)
    cout << R << endl;
    return 0;
}
▶ 运行结果
输入:
3
11 6
25 9
33 17

输出: 809

逐步展开:
【初始】R = 0,M = 1(还没有任何约束)

【第1个方程 a=11, b=6】
  解 M*t ≡ (b-R) (mod a):  1*t ≡ 6 (mod 11)
  g = gcd(1, 11) = 1,C = 6,6 % 1 = 0 ✓
  newM = lcm(1, 11) = 11,inv = 1,t = 6
  R = 0 + 1*6 = 6,M = 11          → x ≡ 6 (mod 11)

【第2个方程 a=25, b=9】
  解 11*t ≡ (9-6) = 3 (mod 25)
  g = gcd(11, 25) = 1,C = 3,3 % 1 = 0 ✓
  exgcd(11, 25) 给出 x = -9(因为 11*(-9) + 25*4 = 1)
  inv = (-9) mod 25 = 16,t = 3 * 16 mod 25 = 48 mod 25 = 23
  newM = lcm(11, 25) = 275
  R = 6 + 11*23 = 259,M = 275       → x ≡ 259 (mod 275)

【第3个方程 a=33, b=17】
  解 275*t ≡ (17-259) = -242 (mod 33)
  C = ((-242) % 33 + 33) % 33 = (-11 + 33) % 33 = 22   ← 非负归一化很关键
  g = gcd(275, 33) = 11,22 % 11 = 0 ✓ 有解
  exgcd(275, 33) 给出 x = 1(因为 275*1 + 33*(-8) = 275 - 264 = 11 = g)
  mg = 33 / 11 = 3,inv = 1 mod 3 = 1
  t = (22/11) * 1 mod 3 = 2 * 1 mod 3 = 2
  newM = lcm(275, 33) = 825
  R = 259 + 275*2 = 809,M = 825     → x ≡ 809 (mod 825)

【输出】R mod M = 809

验算: 809 mod 11 = 6 ✓,809 mod 25 = 9 ✓,809 mod 33 = 17 ✓

注意第三步的 C:(−242) % 33 在 C++ 里等于 −11(不是 22),必须写成 ((b - R) % a + a) % a 才能拿到正确的非负余数 22。这是 exCRT 实现中最容易翻车的一行 —— 一旦 C 为负,C % g 的符号也随之混乱,"有解"可能被误判成"无解"。

三个易错点:① C 未归一化;② 逆元用错对象(应求 M/g 模 a/g 的逆元,不是 M 模 a 的);③ R 更新时忘记先对 newM 取模,导致数值爆炸。

案例案例3:把大模数拆成互质因子再合并 进阶用法

场景:题目要求答案对 P = 999911659 取模,但计算过程中需要求一个巨大的指数(比如 G^N)。由费马小定理,指数可以对 P − 1 = 999911658 取模 —— 可是这个数也不小,而且我们往往只能分别算出它对几个小模数的余数。

关键观察:999911658 = 2 × 3 × 4679 × 35617,其中 2、3、4679、35617 都是素数且两两互质!这意味着:

已知:某个指数 e 对 2、3、4679、35617 的余数分别是 e₁, e₂, e₃, e₄。

要求:e 对 M = 999911658 的余数。

解法:这正是 CRT 的标准形式!算出 Mᵢ = M / mᵢ 与逆元 tᵢ,然后 e ≡ Σ eᵢ·Mᵢ·tᵢ (mod M)。

数值验证:取 e = 123456789(它小于 999911658)。分别取模得 e₁ = 1、e₂ = 0、e₃ = 1374、e₄ = 8267。用 CRT 合并,结果恰好回到 123456789 ✓。这说明"拆开算、合起来"是完全无损的。

为什么这样做有优势?因为每个小模数下都能用更简单的工具:模 2 和模 3 的组合数用卢卡斯定理直接算;模 4679 和 35617 是小素数,用卢卡斯定理 + 阶乘预处理也能快速算出来。四个小模数各自轻松解决后,用一次 CRT 把它们拼成一个模 999911658 的答案 —— 这就是"分而治之"在数论里的经典体现。

C++案例3 · 把四个小模数的余数合并回大模数
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}

int main() {
    // 四个两两互质的小模数,乘积 = 999911658
    vector<ll> m = {2, 3, 4679, 35617};
    ll M = 999911658;

    // 假设这是各个小模数下分别算出的余数(示例值)
    vector<ll> a = {1, 0, 1374, 8267};

    i128 ans = 0;
    for (int i = 0; i < 4; i++) {
        ll Mi = M / m[i];
        ll ti, y;
        exgcd(Mi % m[i], m[i], ti, y);
        ti = (ti % m[i] + m[i]) % m[i];
        ans = (ans + (i128)a[i] * (Mi % M) % M * ti) % M;
    }
    cout << (ll)ans << endl;         // 应输出 123456789
    return 0;
}
▶ 运行结果
输出: 123456789

说明: 各小模数下的余数
      123456789 mod 2     = 1
      123456789 mod 3     = 0
      123456789 mod 4679  = 1374
      123456789 mod 35617 = 8267
    CRT 合并后精确还原出 123456789 ✓

这一步在算法竞赛中的地位:它把"模一个 10 亿级的合数"拆成"模四个小的素数",让原本无法使用卢卡斯定理的情形变得可以使用。P2480 [SDOI2010] 古代猪文、P4720 【模板】扩展卢卡斯、以及任意模数多项式乘法(MTT)都依赖这个套路。学完本讲,你已经掌握了它的全部核心机制。

模块 03

实践应用

三道递进题:先手算一遍 CRT,再练两方程合并的判无解,最后用 exCRT 处理多个不互质的方程。

题1计算 + 编程

手算并编程验证:解同余方程组 x ≡ 1 (mod 4)、x ≡ 2 (mod 5)、x ≡ 3 (mod 7)。

查看参考答案

手算过程:三个模数 4、5、7 两两互质,可以用 CRT。

第一步:M = 4 × 5 × 7 = 140。M₁ = 140/4 = 35,M₂ = 140/5 = 28,M₃ = 140/7 = 20。

第二步(求逆元):

t₁:35t ≡ 1 (mod 4) → 3t ≡ 1 (mod 4) → t₁ = 3(3×3 = 9 ≡ 1)。

t₂:28t ≡ 1 (mod 5) → 3t ≡ 1 (mod 5) → t₂ = 2(3×2 = 6 ≡ 1)。

t₃:20t ≡ 1 (mod 7) → 6t ≡ 1 (mod 7) → t₃ = 6(6×6 = 36 = 35 + 1 ≡ 1)。

第三步(三项求和):1×35×3 + 2×28×2 + 3×20×6 = 105 + 112 + 360 = 577。

第四步(取模):577 mod 140 = 577 − 4×140 = 577 − 560 = 17。

验证:17 mod 4 = 1 ✓,17 mod 5 = 2 ✓,17 mod 7 = 3 ✓。全部正确!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;
ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}
int main() {
    vector<ll> a = {1, 2, 3}, m = {4, 5, 7};
    ll M = 1;
    for (ll mi : m) M *= mi;
    i128 ans = 0;
    for (int i = 0; i < 3; i++) {
        ll Mi = M / m[i], ti, y;
        exgcd(Mi % m[i], m[i], ti, y);
        ti = (ti % m[i] + m[i]) % m[i];
        ans = (ans + (i128)a[i] * Mi % M * ti) % M;
    }
    cout << (ll)ans << endl;   // 17
    return 0;
}

易错:求逆元时忘记取模得到负数;三项求和后忘记对 M 取模(会输出 577 而不是 17)。

题2综合编程 · 两方程合并

输入两个同余方程的参数 (m₁, r₁) 与 (m₂, r₂),若相容则输出合并后的等价方程 x ≡ R (mod M);若矛盾则输出 No Solution。

数据范围:1 ≤ m₁, m₂ ≤ 10⁹,0 ≤ r₁, r₂ < 对应的模数。

查看参考答案

思路:设 x = r₁ + m₁t,代入第二式得 m₁t ≡ r₂ − r₁ (mod m₂)。g = gcd(m₁, m₂),若 g ∤ (r₂ − r₁) 则矛盾;否则求出 t 的最小非负解,R = r₁ + m₁t,M = lcm(m₁, m₂)。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}

int main() {
    ll m1, r1, m2, r2; cin >> m1 >> r1 >> m2 >> r2;
    // 解 m1 * t ≡ (r2 - r1) (mod m2)
    ll C = ((r2 - r1) % m2 + m2) % m2;
    ll x, y;
    ll g = exgcd(m1, m2, x, y);
    if (C % g) { cout << "No Solution" << endl; return 0; }

    ll mg2 = m2 / g;
    ll inv = (x % mg2 + mg2) % mg2;        // (m1/g) 在 mod (m2/g) 下的逆元
    ll t = (ll)((i128)(C / g) % mg2 * inv % mg2);

    ll M = m1 / g * m2;                    // lcm(m1, m2)
    ll R = (ll)(((i128)r1 + (i128)m1 % M * t) % M);
    cout << R << " " << M << endl;
    return 0;
}

样例一:输入 4 3 6 5 → 输出 11 12。验证:11 mod 4 = 3 ✓,11 mod 6 = 5 ✓。

样例二:输入 4 1 6 2 → 输出 No Solution。g = gcd(4,6) = 2,r₂ − r₁ = 1 不被 2 整除。

样例三(互质情形):输入 3 2 5 3 → 输出 8 15。验证:8 mod 3 = 2 ✓,8 mod 5 = 3 ✓,与 CRT 的结果一致。

易错:C 未做非负归一化;M 用 m1 * m2 而不是 m1/g*m2(差一个 g);逆元求成 m1 模 m2 的而不是 m1/g 模 m2/g 的。

题3综合编程 · exCRT

输入 n(n ≤ 10⁵)个同余方程 x ≡ rᵢ (mod mᵢ),模数不保证两两互质,求最小非负整数解;若矛盾输出 −1。

数据范围:1 ≤ mᵢ ≤ 10¹²,0 ≤ rᵢ < mᵢ,保证答案在 long long 内。

查看参考答案

思路:维护当前合并结果 x ≡ R (mod M),初值 R = 0、M = 1。每读入一个方程就做一次合并。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}

int main() {
    int n; cin >> n;
    ll R = 0, M = 1;
    for (int i = 0; i < n; i++) {
        ll m, r; cin >> m >> r;
        ll C = ((r - R) % m + m) % m;
        ll x, y;
        ll g = exgcd(M, m, x, y);
        if (C % g) { cout << "-1" << endl; return 0; }
        ll mg = m / g;
        ll inv = (x % mg + mg) % mg;
        ll t = (ll)((i128)(C / g) % mg * inv % mg);
        ll newM = M / g * m;
        R = (ll)(((i128)R + (i128)(M % newM) % newM * t) % newM);
        M = newM;
    }
    cout << (R % M + M) % M << endl;
    return 0;
}

样例一(有解):输入 3 / 6 4 / 8 6 / 9 7 → 输出 70。验证:70 mod 6 = 4 ✓、70 mod 8 = 6 ✓、70 mod 9 = 7 ✓。中间过程:先合并前两式得 x ≡ 22 (mod 24),再与 x ≡ 7 (mod 9) 合并得 x ≡ 70 (mod 72)。

样例二(无解):输入 3 / 6 4 / 8 6 / 9 8 → 输出 −1。前两式合并仍是 x ≡ 22 (mod 24),即 x ≡ 1 (mod 3);而第三式要求 x ≡ 8 ≡ 2 (mod 3),矛盾。

样例三(与 CRT 对比):输入 3 / 3 2 / 5 3 / 7 2 → 输出 23,与 CRT 构造法结果一致。

易错:初值写成 R = 1、M = 1;合并时用 m1 * m2 而非 lcm;中间乘法溢出(必须 __int128);n = 10⁵ 时用 cin 不加 ios::sync_with_stdio(false) 可能读入偏慢。

模块 04

总结提升

把 CRT 与 exCRT 的公式、判据、模板收成一张表,再用三个拓展案例把它推到更远处。

📌核心要点一览
知识点结论 / 公式关键前提
同余方程组x ≡ aᵢ (mod mᵢ),i = 1..n—
CRT 前提m₁, …, mₙ 两两互质缺此条件不可用构造法
CRT 总模数M = m₁·m₂·…·mₙ即解的周期
CRT 分量Mᵢ = M / mᵢmⱼ | Mᵢ(j ≠ i),mᵢ ∤ Mᵢ
CRT 逆元tᵢ = Mᵢ⁻¹ (mod mᵢ),用 exgcd 求gcd(Mᵢ, mᵢ) = 1 由互质性保证
CRT 解x ≡ Σ aᵢ·Mᵢ·tᵢ (mod M)模 M 下唯一
exCRT 合并式m₁·t ≡ r₂ − r₁ (mod m₂)由 x = r₁ + m₁t 代入得到
exCRT 判无解g = gcd(m₁, m₂) 不整除 (r₂ − r₁) → 无解g = 1 时必不整除,必相容
exCRT 新模数lcm(m₁, m₂) = m₁·m₂ / g不能用乘积
exCRT 合并结果x ≡ r₁ + m₁·t₀ (mod lcm)t₀ 为 t 的最小非负解
复杂度CRT O(n log m);exCRT O(n log m)n 次 exgcd
溢出处理中间乘法一律走 __int128aᵢMᵢtᵢ 可达 M² 量级
⌨️本讲必背模板(一份可直接抄的代码)
C++CRT 工具箱:exgcd + CRT + exCRT
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}

// ① CRT:要求 m 两两互质,返回最小非负解
ll crt(vector<ll> &a, vector<ll> &m) {
    ll M = 1;
    for (ll mi : m) M *= mi;
    i128 ans = 0;
    for (size_t i = 0; i < a.size(); i++) {
        ll Mi = M / m[i], ti, y;
        exgcd(Mi % m[i], m[i], ti, y);
        ti = (ti % m[i] + m[i]) % m[i];
        ans = (ans + (i128)(a[i] % m[i]) * Mi % M * ti) % M;
    }
    return (ll)ans;
}

// ② exCRT:不要求互质,返回 {true, 解} 或 {false, -1}
pair<bool, ll> excrt(vector<ll> &a, vector<ll> &m) {
    ll R = 0, M = 1;
    for (size_t i = 0; i < a.size(); i++) {
        ll A = M, B = m[i], C = ((a[i] - R) % B + B) % B;
        ll x, y;
        ll g = exgcd(A, B, x, y);                // A*x + B*y = g
        if (C % g) return {false, -1};
        ll Bg = B / g;
        ll inv = (x % Bg + Bg) % Bg;
        ll t = (ll)((i128)(C / g) % Bg * inv % Bg);
        ll newM = M / g * B;
        R = (ll)(((i128)R + (i128)(M % newM) % newM * t) % newM);
        M = newM;
    }
    return {true, (R % M + M) % M};
}

int main() {
    vector<ll> a1 = {2, 3, 2}, m1 = {3, 5, 7};
    cout << crt(a1, m1) << endl;               // 23

    vector<ll> a2 = {4, 6, 7}, m2 = {6, 8, 9};
    auto [ok, res] = excrt(a2, m2);
    cout << (ok ? res : -1) << endl;         // 70

    vector<ll> a3 = {4, 6, 8}, m3 = {6, 8, 9};
    auto [ok2, res2] = excrt(a3, m3);
    cout << (ok2 ? res2 : -1) << endl;        // -1
    return 0;
}
▶ 运行结果
23
70
-1

逐一验证:
crt({2,3,2}, {3,5,7}) = 23             物不知数 ✓
excrt({4,6,7}, {6,8,9}) = 70           70 mod 6 = 4 ✓,mod 8 = 6 ✓,mod 9 = 7 ✓
excrt({4,6,8}, {6,8,9}) = 无解         前两式合并得 x ≡ 22 (mod 24),而 22 mod 3 = 1,
                                        第三式要求 x ≡ 8 ≡ 2 (mod 3),矛盾 ✓
🚀拓展案例
拓展 1:CRT 与 exCRT 到底该用哪个?
对比项CRT(中国剩余定理)exCRT(扩展中国剩余定理)
模数要求两两互质无要求
能否判无解不需要(互质时必有解)能,用 g | (r₂−r₁) 判断
核心工具exgcd 求 Mᵢ 的逆元exgcd 解一次同余方程
解的形式x ≡ ΣaᵢMᵢtᵢ (mod ∏mᵢ)逐步合并,x ≡ R (mod lcm)
实现复杂度简单(一次循环,一遍求和)稍复杂(边合并边更新)
数据范围敏感度需要算 ∏mᵢ,n 大时可能溢出边算边取模,天然抗溢出
推荐场景模数已保证互质(如曹冲养猪)模数任意、需要判无解、n 很大

💡 实战建议

如果题目没有明确保证模数互质,一律用 exCRT —— 它在互质时结果与 CRT 完全相同,但适用范围更广,还能顺带发现无解。只有在你确定模数互质、且想让代码更短时,才考虑 CRT 构造法。

不过有一个例外:当"模数互质"是题目的核心考点(比如要求你证明唯一性、或者需要用 M = ∏mᵢ 做别的运算),那还是老老实实写 CRT。

拓展 2:当模数是素数幂 —— 从 CRT 到混合进制(Garner 算法)

CRT 要求模数互质,而"素数幂"是一类天然互质的模数:p₁^e₁, p₂^e₂, …。所以任何合数模数都可以按质因数分解拆成素数幂,分别求解后用 CRT 合并。

但有一个更聪明的做法:既然 CRT 的结果是唯一的,我们可以把它写成混合进制的形式,逐位确定系数,完全避开求逆元时的大数运算。这就是 Garner 算法。

思路:假设答案 x(模 M = m₁m₂…mₙ)可以写成

x = c₁ + c₂m₁ + c₃m₁m₂ + … + cₙm₁m₂…m_{n−1}

其中系数满足 0 ≤ cᵢ < mᵢ。这种写法叫混合进制(mixed radix),就像时间用"时:分:秒"三级单位一样。

怎么定系数?逐位代入同余条件:

① 对 m₁ 取模:x ≡ c₁ (mod m₁),直接得 c₁ = a₁。

② 对 m₂ 取模:c₁ + c₂m₁ ≡ a₂ (mod m₂),解出 c₂ = (a₂ − c₁)·inv(m₁) (mod m₂)。

③ 一般地,cᵢ = (aᵢ − Σ_{j<i} cⱼ·∏_{k<j} m_k) · inv(∏_{k<i} m_k) (mod mᵢ)。

优势:每一步的模数只有 mᵢ 那么大(不是整个 M),所以中间结果的数值规模被压到最小,非常适合需要"小素数模数分别计算"的场景。

为什么它有用?因为有些"模数"根本不是整数(例如多项式的模 x²+1),或者模数太大无法直接运算。Garner 算法把问题拆成若干个"小模数下的小问题",这是 CRT 思想最极致的体现。

拓展 3:CRT 的工程应用 —— 任意模数多项式乘法(MTT)

多项式乘法常用 FFT/NTT 加速,但 NTT 要求模数是 2^k·c + 1 形式的特殊素数(如 998244353)。如果题目要求结果模 10⁹+7 这种"不支持 NTT"的数怎么办?

解法(MTT,即"任意模数 NTT"):

1
3️⃣
挑三个 NTT 模数

例如 998244353、1004535809、469762049,它们的乘积约 4.7×10²⁶,远超单个系数可能的最大值。

2
🔁
分别做三次 NTT

在每个模数下独立做一次多项式乘法,得到三组结果。

3
🧩
用 CRT 合并

对每个系数,用 CRT 把它从三个小模数合并回真实值(因为真实值小于三个模数之积,合并结果就是精确值)。

4
✂️
再对目标模数取模

得到精确系数后,再对 10⁹+7 取模即可。

这就是 CRT 在算法竞赛中最"硬核"的应用之一。它的思想内核和本讲完全一致:把一个"大模数下算不了"的问题,拆成几个"小模数下算得了"的问题,再用 CRT 拼回来。P2480 古代猪文把 999911658 拆成 2 × 3 × 4679 × 35617,用的正是同一个套路。

🤔思考与启发

思考1:CRT 构造法里的逆元 tᵢ 不是唯一的(模 mᵢ 下有无穷多个代表)。取不同的 tᵢ 会得到不同的 x 吗?最终答案会变吗?

查看参考答案

会得到不同的 x,但它们是同一个同余类的不同代表。

理由:若 tᵢ′ = tᵢ + k·mᵢ 也是 Mᵢ 的逆元(因为 Mᵢ·tᵢ′ = Mᵢtᵢ + k·Mᵢmᵢ = Mᵢtᵢ + kM ≡ Mᵢtᵢ (mod mᵢ)),那么第 i 项 aᵢMᵢtᵢ′ 与 aᵢMᵢtᵢ 的差是 aᵢ·k·M,它是 M 的倍数。

求和后,不同 tᵢ 选择造成的差异是若干个 M 的倍数之和,仍然是 M 的倍数。所以两个 x 只差 M 的整数倍,模 M 下完全相等。

实践意义:正因为如此,我们可以放心地把 tᵢ 归一到 [0, mᵢ),不需要担心"选错"逆元 —— 只要保证 Mᵢ·tᵢ ≡ 1 (mod mᵢ) 即可。

易错:以为"不同逆元 → 不同答案 → 题目有歧义"。实际上题目问的都是"最小非负解",归一化后必然唯一。

思考2:CRT 要求模数"两两互质"。如果放松成"整体互质"(即 gcd(m₁, m₂, …, mₙ) = 1),结论还成立吗?请举例说明。

查看参考答案

不成立。"两两互质"是比"整体互质"强得多的条件,不能替换。

反例:取 m₁ = 6、m₂ = 10、m₃ = 15。整体 gcd 是 gcd(6, 10, 15) = 1 ✓,但两两 gcd 分别等于 2、3、5,都不是 1。

此时乘积 M = 6 × 10 × 15 = 900,而真正的最小公倍数 lcm = 30。两者差了 30 倍,说明"模 M 下唯一解"的周期被夸大了。

具体后果:CRT 的构造法会失效 —— M₁ = M/6 = 150,而 gcd(150, 6) = 6 ≠ 1,所以 M₁ 在模 6 下根本没有逆元,构造公式写不下去。

正确做法:改用 exCRT 逐次合并,得到周期为 lcm = 30 的解。或者先把每个模数分解成素数幂,只保留"同一素数下的最高次幂"(6 = 2×3、10 = 2×5、15 = 3×5 → 提取 2、3、5 各一次),拆成互质的模数再用 CRT。

易错:把"整体互质"当成"两两互质";或者在模数不互质时硬套 CRT 构造法,结果卡在"逆元不存在"上。

思考3:中国剩余定理断言"模 M 下解唯一"。如果把"整数解"改成"正整数解"或"x 在某个区间内的解",情况会怎样变化?如何求出第 k 个正整数解?

查看参考答案

正整数解有无穷多个。因为若 x₀ 是最小非负解,则 x₀ + t·M(t 为任意整数)都是解。取 t 足够大即可得到无穷多个正整数解(若 x₀ = 0,则 t ≥ 1)。

求第 k 个正整数解:设最小非负解为 x₀(若 x₀ = 0 则第 1 个正整数解是 M),则第 k 个正整数解为 x₀ + (k − 1)·M(当 x₀ > 0),或 k·M(当 x₀ = 0)。

求区间 [L, R] 内的解个数:个数 = ⌊(R − x₀) / M⌋ − ⌈(L − x₀) / M⌉ + 1(若结果 ≤ 0 则为 0)。这与 s6-05 里"不定方程在区间内解的个数"的算法完全同构。

求最小解大于某个值的情形:例如"求最小的满足条件的数,且该数 ≥ 1000",只要 x = x₀ + ⌈(1000 − x₀)/M⌉·M 即可。

易错:把"最小非负解"和"最小正整数解"混为一谈(当 x₀ = 0 时两者不同);求区间个数时忘记处理负数除法(要用 floor_div)。

模块 05

学习检测

先找错,再背口诀 —— 把易错点消灭在考试之前。

易错1

模数不互质却用 CRT 构造法

❌ 错误
对 x ≡ 4 (mod 6)、x ≡ 6 (mod 8) 直接套 CRT:M = 48,M₁ = 8,然后求 8t ≡ 1 (mod 6) —— 但 gcd(8, 6) = 2,逆元根本不存在,exgcd 会返回 g = 2 而程序还在继续算。
✅ 正确
使用 CRT 前先验证"两两互质"。不互质就改用 exCRT:设 x = 4 + 6t 代入第二式得 6t ≡ 2 (mod 8),g = 2 | 2 ✓,解得 t ≡ 3 (mod 4),最终 x ≡ 22 (mod 24)。
口诀:构造法先看互质,不互质改用合并法!
易错2

exCRT 中 C 未做非负归一化

❌ 错误
ll C = (b - R) % a;。当 b − R 为负时,C 也是负数(如 (−242) % 33 = −11),后续 C % g 的符号混乱,可能把有解判成无解,或算出错误的 t。
✅ 正确
ll C = ((b - R) % a + a) % a; —— 加一个模数再取模,把 C 拉进 [0, a)。这是 exCRT 里最关键、也最容易漏掉的一行。
口诀:C 必须非负,先加模再取模!
易错3

新模数写成乘积而不是 lcm

❌ 错误
合并后写 M = M * m;。当 gcd(M, m) = g > 1 时,真实周期是 lcm 而不是乘积。例如合并 6 与 8 时,写 48 会导致后续所有同余判断全部偏移。
✅ 正确
M = M / g * m;(先除后乘,既得 lcm 又避免中间溢出)。注意顺序必须是先除 g 再乘 m,写成 M * m / g 可能溢出。
口诀:新模数是 lcm,先除 g 再乘 m!
易错4

CRT 求和时忘记取模导致溢出

❌ 错误
ans += a[i] * Mi * ti; 用 long long 累加。aᵢ·Mᵢ·tᵢ 可达 M² 量级,M 到 10⁹ 时 M² = 10¹⁸ 已逼近 long long 上限,再加几项立刻溢出成负数。
✅ 正确
每项都用 (__int128) 中转,并且逐项取模:ans = (ans + (i128)a[i] * Mi % M * ti) % M;。注意 % M 的位置 —— 先让 aᵢ·Mi 对 M 取模,再乘 tᵢ,最后再取模。
口诀:大数乘法上 __int128,逐项取模别攒着!
易错5

把题目给的输入顺序读反

❌ 错误
题目写"每行两个整数 aᵢ bᵢ",其中 aᵢ 是余数、bᵢ 是模数,你按"模数在前"写成 cin >> m >> a,结果所有数据都对不上,样例全错。
✅ 正确
先读题确认顺序。CRT 类题目中 P1495 是"模数在前、余数在后",P4777 是"模数在前、余数在后",而有些题目(如部分版本的猜数字)是"余数在前"。用样例验证一遍是最稳的做法。
口诀:先看样例再动手,模数余数别读反!
📋速查表
结论 / 公式内容前提条件
同余方程组x ≡ aᵢ (mod mᵢ)i = 1..n
CRT 适用条件m₁, …, mₙ 两两互质否则改用 exCRT
CRT 总模数M = m₁m₂…mₙ= lcm,即解周期
CRT 分量Mᵢ = M / mᵢmⱼ | Mᵢ (j≠i)
CRT 逆元tᵢ = Mᵢ⁻¹ mod mᵢ(exgcd 求)gcd(Mᵢ, mᵢ) = 1
CRT 解公式x ≡ Σ aᵢMᵢtᵢ (mod M)最小非负解取 mod M
解的唯一性模 M 下唯一,整数上无穷多周期为 M
exCRT 合并式M·t ≡ aᵢ − r (mod mᵢ)由 x = r + M·t 代入
exCRT 判无解gcd(M, mᵢ) ∤ (aᵢ − r) → 无解这是唯一的无解判据
exCRT 新模数M′ = M / g · mᵢ = lcm不能用乘积
exCRT 初值R = 0,M = 1模 1 下所有数同余
复杂度O(n log(max mᵢ))每次合并一次 exgcd
📌笔记卡
必记概念
  • CRT 前提:模数两两互质
  • Mᵢ = M/mᵢ,tᵢ = Mᵢ 的逆元
  • x ≡ ΣaᵢMᵢtᵢ (mod M)
  • exCRT 合并:m₁t ≡ r₂−r₁ (mod m₂)
  • 新模数是 lcm,不是乘积
必背模板
  • CRT:算 M → 求逆元 → 加权求和
  • exCRT:R=0,M=1,逐个合并
  • C = ((b−R)%a+a)%a 归一化
  • M = M/g*m 先除后乘
  • 全部乘法走 __int128
考场提醒
  • 先判断模数互不互质
  • 不互质就用 exCRT 并判无解
  • C 一定要非负
  • 读入顺序看样例确认
  • 拆模数求素数幂可降低规模
课后练习

分层练习作业

A基础 / B提高 / C挑战,难度递增。C组后附 12 道洛谷原题。

🟢 A组 · 基础(4题)

A1计算题

手算"物不知数":解 x ≡ 2 (mod 3)、x ≡ 3 (mod 5)、x ≡ 2 (mod 7),写出完整的构造过程。

查看参考答案

第一步:M = 3 × 5 × 7 = 105。M₁ = 105/3 = 35、M₂ = 105/5 = 21、M₃ = 105/7 = 15。

第二步(求逆元):

t₁:35t ≡ 1 (mod 3) → 2t ≡ 1 (mod 3) → t₁ = 2。

t₂:21t ≡ 1 (mod 5) → 1·t ≡ 1 (mod 5) → t₂ = 1。

t₃:15t ≡ 1 (mod 7) → 1·t ≡ 1 (mod 7) → t₃ = 1。

第三步(三项):2×35×2 = 140,3×21×1 = 63,2×15×1 = 30。

第四步(求和取模):140 + 63 + 30 = 233,233 mod 105 = 23。

验证:23 mod 3 = 2 ✓,23 mod 5 = 3 ✓,23 mod 7 = 2 ✓。全部解为 x ≡ 23 (mod 105)。

易错:t₁ 算成 1(2×1 = 2 ≠ 1);求和后忘记取模输出 233。

A2编程题

实现 CRT:输入 n 组 (mᵢ, aᵢ),保证 mᵢ 两两互质,输出 x ≡ aᵢ (mod mᵢ) 的最小非负解。

查看参考答案

思路:直接套模板,注意用 __int128 做中间乘法。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;
ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}
int main() {
    int n; cin >> n;
    vector<ll> m(n), a(n);
    for (int i = 0; i < n; i++) cin >> m[i] >> a[i];
    ll M = 1;
    for (int i = 0; i < n; i++) M *= m[i];
    i128 ans = 0;
    for (int i = 0; i < n; i++) {
        ll Mi = M / m[i], ti, y;
        exgcd(Mi % m[i], m[i], ti, y);
        ti = (ti % m[i] + m[i]) % m[i];
        ans = (ans + (i128)(a[i] % m[i]) * Mi % M * ti) % M;
    }
    cout << (ll)ans << endl;
    return 0;
}

样例:输入 3 / 3 2 / 5 3 / 7 2 → 输出 23。

易错:逆元未归一化;求和未逐项取模;M 的累乘溢出。

A3计算题

解同余方程组 x ≡ 3 (mod 4)、x ≡ 2 (mod 3)、x ≡ 5 (mod 7)。

查看参考答案

第一步:4、3、7 两两互质,M = 4 × 3 × 7 = 84。M₁ = 84/4 = 21、M₂ = 84/3 = 28、M₃ = 84/7 = 12。

第二步(逆元):

t₁:21t ≡ 1 (mod 4) → 1·t ≡ 1 (mod 4) → t₁ = 1。

t₂:28t ≡ 1 (mod 3) → 1·t ≡ 1 (mod 3) → t₂ = 1。

t₃:12t ≡ 1 (mod 7) → 5t ≡ 1 (mod 7) → t₃ = 3(5×3 = 15 = 14 + 1 ≡ 1)。

第三步:3×21×1 + 2×28×1 + 5×12×3 = 63 + 56 + 180 = 299。

第四步:299 mod 84 = 299 − 3×84 = 299 − 252 = 47。

验证:47 mod 4 = 3 ✓,47 mod 3 = 2 ✓,47 mod 7 = 5 ✓(47 = 42 + 5)。

易错:t₃ 算成 1 或 2;把 M₁ 和 M₂ 算反。

A4证明题

证明 CRT 中构造出的 x = Σ aᵢMᵢtᵢ 满足所有同余条件。

查看参考答案

已知:m₁, …, mₙ 两两互质,M = m₁m₂…mₙ,Mᵢ = M/mᵢ,tᵢ 满足 Mᵢtᵢ ≡ 1 (mod mᵢ)。要证对任意 i 有 x ≡ aᵢ (mod mᵢ)。

证明:固定一个下标 i,把 x 拆成第 i 项与其余项之和:

x = aᵢMᵢtᵢ + Σ_{j≠i} aⱼMⱼtⱼ

处理求和部分:对任意 j ≠ i,因为 mᵢ 与 mⱼ 互质,且 Mⱼ = M/mⱼ 中包含了因子 mᵢ(M 里含有 mᵢ,而除掉的 mⱼ ≠ mᵢ),所以 mᵢ | Mⱼ,即 Mⱼ ≡ 0 (mod mᵢ)。

于是每一项 aⱼMⱼtⱼ ≡ aⱼ · 0 · tⱼ ≡ 0 (mod mᵢ),求和也为 0。

处理第 i 项:由 tᵢ 的定义 Mᵢtᵢ ≡ 1 (mod mᵢ),得 aᵢMᵢtᵢ ≡ aᵢ · 1 = aᵢ (mod mᵢ)。

合起来:x ≡ aᵢ + 0 ≡ aᵢ (mod mᵢ)。由于 i 是任取的,x 满足全部 n 个条件。∎

证明的关键:① "mᵢ | Mⱼ(j ≠ i)"这一步必须依赖 mᵢ 与 mⱼ 互质 —— 否则 mᵢ 可能在 Mⱼ 中被约掉;② tᵢ 的定义恰好抵消掉 Mᵢ 的"残余系数"。

易错:漏掉"由互质性保证 mᵢ | Mⱼ"这一步,直接说 Mⱼ 是 mᵢ 的倍数;或者把 tᵢ 的性质写成 Mᵢtᵢ ≡ 1 (mod M)(那是错的,只能是 mod mᵢ)。

🟡 B组 · 提高(4题)

B1综合编程

输入两个同余方程 (m₁, r₁)、(m₂, r₂),输出合并后的等价方程;若矛盾输出 −1。

查看参考答案

思路:设 x = r₁ + m₁t,代入第二式得 m₁t ≡ r₂ − r₁ (mod m₂),用 exgcd 求解并判有无解。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;
ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}
int main() {
    ll m1, r1, m2, r2; cin >> m1 >> r1 >> m2 >> r2;
    ll C = ((r2 - r1) % m2 + m2) % m2;
    ll x, y;
    ll g = exgcd(m1, m2, x, y);
    if (C % g) { cout << "-1" << endl; return 0; }
    ll mg = m2 / g;
    ll inv = (x % mg + mg) % mg;
    ll t = (ll)((i128)(C / g) % mg * inv % mg);
    ll M = m1 / g * m2;
    ll R = (ll)(((i128)r1 + (i128)m1 % M * t) % M);
    cout << R << " " << M << endl;
    return 0;
}

样例:输入 4 3 6 5 → 输出 11 12;输入 4 1 6 2 → 输出 -1;输入 6 4 8 6 → 输出 22 24。

考点:exCRT 的单次合并操作,是理解整个 exCRT 的基础。

易错:C 未归一化;新模数写成 m1*m2;逆元求错对象。

B2综合编程

【P1495 曹冲养猪】输入 n 组 (mᵢ, aᵢ),mᵢ 两两互质,求最小非负解。

查看参考答案

思路:CRT 模板题,代码见模块二 #crt-code 或 A2。

样例:输入 3 / 3 1 / 5 2 / 7 3 → 输出 52。

过程:M = 105;三项分别为 1×35×2 = 70、2×21×1 = 42、3×15×1 = 45;求和 157;157 mod 105 = 52。

易错:忘记 Mi % m[i];输出未取模。

B3分析题

为什么 CRT 要求模数"两两互质"?请说明这个条件在构造法的哪一步被用到,并给出一个不互质时构造法失效的具体例子。

查看参考答案

用到的地方有两处:

① 求逆元那一步(最致命)。构造法需要 Mᵢ = M/mᵢ 在模 mᵢ 下可逆,即要求 gcd(Mᵢ, mᵢ) = 1。而 Mᵢ 里含有除 mᵢ 之外的全部模数,所以只要有一个 mⱼ(j ≠ i)与 mᵢ 有公因数,gcd(Mᵢ, mᵢ) 就大于 1,逆元不存在,公式写不下去。

② 总模数取乘积那一步。CRT 说"周期是 M = ∏mᵢ",这依赖"两两互质时 ∏mᵢ = lcm"。不互质时真实周期是 lcm < ∏mᵢ,用乘积会得到一个偏大的周期,导致唯一性结论错误。

具体例子:解 x ≡ 4 (mod 6) 与 x ≡ 6 (mod 8)。

M = 6 × 8 = 48,M₁ = 48/6 = 8,需要求 8t ≡ 1 (mod 6)。但 gcd(8, 6) = 2,8 的任何倍数除以 6 的余数都是偶数,永远不等于 1 —— 逆元不存在,构造法完全卡住。

正确结果:这两式其实有解。x ≡ 4 (mod 6) 的解集是 {4, 10, 16, 22, …},x ≡ 6 (mod 8) 的解集是 {6, 14, 22, …},公共部分是 {22, 46, 70, …},即 x ≡ 22 (mod 24)。周期是 lcm(6, 8) = 24,而不是 48。

结论:"两两互质"是构造法成立的充分条件(不是必要条件,某些不互质的情形也能构造,但要额外处理),而 exCRT 通过"合并"避开了这个限制。

易错:以为"整体互质"就够了;或者以为不互质就一定无解(上面的例子就有解)。

B4综合编程

给定一个大模数 P = 1 000 000 007 = 2 × 500000003(注意 500000003 需自行验证是否为素数)。若题目要求"答案对 P 取模",但你的程序只能分别在模 2 和模 500000003 下计算,如何拼回模 P 的答案?请写出代码框架。

查看参考答案

前提澄清:其实 10⁹+7 本身就是素数,不需要拆分。本题旨在练习"拆模数再合并"的技术框架,实际使用时请先确认模数确实可以拆成互质因子。

思路:已知答案 X 满足 X ≡ X₁ (mod 2) 与 X ≡ X₂ (mod 500000003),用 CRT 合并。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}

// 两个互质模数下的余数 → 原模数下的答案
ll mergeTwo(ll r1, ll m1, ll r2, ll m2) {
    ll M = m1 * m2;                       // 互质时即 lcm
    ll M1 = M / m1, M2 = M / m2;
    ll t1, t2, y;
    exgcd(M1 % m1, m1, t1, y);
    exgcd(M2 % m2, m2, t2, y);
    t1 = (t1 % m1 + m1) % m1;
    t2 = (t2 % m2 + m2) % m2;
    i128 s = (i128)r1 * M1 % M * t1 + (i128)r2 * M2 % M * t2;
    return (ll)((s % M + M) % M);
}

int main() {
    ll r1, r2; cin >> r1 >> r2;   // 小模数下分别算出的余数
    cout << mergeTwo(r1, 2, r2, 500000003) << endl;
    return 0;
}

样例:若真实答案 X = 123456789,则 X₁ = 1(123456789 是奇数),X₂ = 123456789 mod 500000003 = 123456789。合并后应输出 123456789。

要点:① 拆分后的模数必须两两互质,所以通常是拆成"素数幂"(如 12 = 4 × 3,而不是 2 × 6);② 合并时 M 可能超出 long long,视数据规模决定是否用 __int128;③ 实际采用这种技巧的场景是"模数是合数、且能用卢卡斯定理在小素数模下快速求组合数"(如 P2480)。

易错:拆成不互质的因子(如 12 = 2 × 6);合并后忘记取模;把"必须互质"的前提当成"任意拆都行"。

🔴 C组 · 挑战(4题)

C1初赛选择题

下列说法正确的有几个?
① 若 m₁, …, mₙ 两两互质,则同余方程组 x ≡ aᵢ (mod mᵢ) 一定有解
② 若模数不两两互质,方程组一定无解
③ CRT 构造出的解在模 ∏mᵢ 下唯一
④ 方程组有解时,整数解的个数有限

A. 1    B. 2    C. 3    D. 4

查看参考答案

答案:B(2 个)

①正确:这正是 CRT 的结论 —— 互质时逆元一定存在,构造必然成功。

②错误:不互质只是"构造法失效",不代表无解。反例:x ≡ 2 (mod 4) 与 x ≡ 2 (mod 6),两模数 gcd = 2 ≠ 1,但 x = 2 就是解(所有满足 x ≡ 2 (mod 12) 的数)。

③正确:唯一性由"两两互质 ⟹ 每个 mᵢ 整除 (x₁ − x₂) ⟹ ∏mᵢ 整除 (x₁ − x₂)"推出。

④错误:若有解 x₀,则 x₀ + kM(k 为任意整数)都是解,所以整数解无穷多。"模 M 下唯一"说的是同余类唯一,不是解的数量有限。

考点:CRT 的适用条件、唯一性的含义、不互质时的情形。

易错:把"模 M 下唯一"误读成"只有唯一一个整数解",从而判断 ④ 正确。

C2构造与证明

不用 CRT 的构造公式,直接用"两两合并"的思路证明:若 m₁, …, mₙ 两两互质,则同余方程组在模 M = ∏mᵢ 下有唯一解。

查看参考答案

归纳的对象:方程的个数 n。

基础情形(n = 1):方程 x ≡ a₁ (mod m₁) 在模 m₁ 下显然有唯一解 a₁ mod m₁。✓

归纳假设:设前 n − 1 个方程合并后的等价形式为 x ≡ R (mod M′),其中 M′ = m₁m₂…m_{n−1},且在模 M′ 下唯一。

归纳步骤:要把 x ≡ R (mod M′) 与 x ≡ aₙ (mod mₙ) 合并。写 x = R + M′t,代入得 M′t ≡ aₙ − R (mod mₙ)。

因为 m₁, …, mₙ 两两互质,所以 gcd(M′, mₙ) = 1(M′ 是前 n − 1 个模数之积,与 mₙ 互质)。于是 e = gcd(M′, mₙ) = 1 整除任何数,方程必有解,且解 t 在模 mₙ 下唯一(因为逆元唯一)。

设唯一解 t₀,则新答案 x ≡ R + M′t₀ (mod M′mₙ)。新模数 M′mₙ = m₁…mₙ = M,且在模 M 下唯一(由 t₀ 的唯一性与 R 的唯一性共同保证)。∎

与构造法的关系:这条证明实际上就是 exCRT 的正确性论证 —— 在互质前提下,exCRT 的每一次合并都不会遇到"g ∤ C"的障碍,所以必然成功,且结果与 CRT 的构造法一致。

易错:归纳假设里忘记说明"前 n − 1 个方程合并后的形式";没有指出 gcd(M′, mₙ) = 1 是由互质性保证的(这一步是"必有解"的唯一依据)。

C3综合编程

【P3868 猜数字】给定 n 组 (aᵢ, bᵢ),求最小的非负整数 x 使得 x ≡ aᵢ (mod bᵢ)。保证 bᵢ 两两互质。

查看参考答案

思路:CRT 模板。注意本题部分数据规模较大,需要用 __int128 处理 aᵢ·Mᵢ·tᵢ 的中间乘法,并且答案不能超过 10¹⁸(题目保证)。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef __int128 i128;

ll exgcd(ll a, ll b, ll &x, ll &y) {
    if (b == 0) { x = 1; y = 0; return a; }
    ll g = exgcd(b, a % b, x, y);
    ll t = x; x = y; y = t - (a / b) * y;
    return g;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    int n; cin >> n;
    vector<ll> a(n), b(n);
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < n; i++) cin >> b[i];

    i128 M = 1;
    for (int i = 0; i < n; i++) M *= b[i];

    i128 ans = 0;
    for (int i = 0; i < n; i++) {
        ll Mi = (ll)(M / b[i] % (i128)b[i]);        // 先对 b[i] 取模,压小参数
        ll ti, y;
        exgcd(Mi, b[i], ti, y);
        ti = (ti % b[i] + b[i]) % b[i];
        ans = (ans + (i128)((a[i] % b[i] + b[i]) % b[i]) * (M / b[i]) % M * ti) % M;
    }
    cout << (ll)ans << endl;
    return 0;
}

样例:输入 3 / 1 2 3 / 3 5 7 → 输出 52(与曹冲养猪的参数相同)。

要点:① 本题读入是"先所有 aᵢ 再所有 bᵢ"(两行),不要按"每行一对"读;② M 用 __int128 存,避免溢出;③ aᵢ 先归一化到 [0, bᵢ) 再参与计算;④ 最终输出转回 long long。

易错:读入顺序搞错(最容易踩的坑);M 用 long long 溢出;求逆元时没把 M/b[i] 对 b[i] 取模。

C4算法设计

设计算法:给定 n 个同余方程(模数不必互质),求满足条件的第 k 个正整数解(k ≤ 10⁹,n ≤ 10⁵)。说明如何处理"无解"与"第 k 个"这两件事。

查看参考答案

第一步(判有无解并算出最小非负解):用 exCRT 合并全部方程,得到 x ≡ R (mod M),或判定无解输出 −1。

第二步(处理"第 k 个正整数解"):合并后的解集是 {R, R + M, R + 2M, …}(当 R > 0 时;注意 R = 0 的情形)。分两种情况:

① 若 R > 0:第 k 个正整数解就是 R + (k − 1)·M。

② 若 R = 0:第 k 个正整数解是 k·M(此时最小的正整数解是 M 而不是 0)。

第三步(防溢出):k 可达 10⁹、M 也可能很大,乘积可能溢出 long long。输出时以十进制字符串形式输出 R + (k−1)M 需要高精度乘法。如果题目要求"对 10⁹+7 取模",则直接算 (R % P + (k−1) % P * (M % P)) % P 即可。

// 在 exCRT 得到 R 与 M 之后(假设有解):
const ll P = 1000000007;
ll k; cin >> k;

if (R == 0) {
    // 第 k 个正整数解是 k*M
    cout << (i128)(k % P) * (M % P) % P << endl;
} else {
    // 第 k 个正整数解是 R + (k-1)*M
    i128 v = (i128)R % P + (i128)((k - 1) % P) * (M % P) % P;
    cout << (ll)(v % P) << endl;
}

样例:若合并结果为 x ≡ 70 (mod 72)(来自前面的例子),则正整数解是 70、142、214、…。第 1 个是 70,第 5 个是 70 + 4×72 = 358。

另一个例子:若合并结果为 x ≡ 0 (mod 24),则正整数解是 24、48、72、…,第 3 个是 72(注意不是 0 + 2×24 = 48,因为 0 不算正整数)。

易错:R = 0 时按 R + (k−1)M 算,得到 (k−1)M 而不是 kM;把 k 和 M 直接相乘导致溢出;把"最小非负解"当成"最小正整数解"。

🏆 洛谷原题精选(12道)

以下 12 道题围绕"中国剩余定理 / 扩展中国剩余定理 / 模数拆分"展开,按难度从提高 到 省选排列。

1
P1082
同余方程【NOIP2012提高组】提高
2
P5656
二元一次不定方程提高
3
P3868
[TJOI2009] 猜数字提高
4
P1495
【模板】中国剩余定理(CRT)/曹冲养猪提高
5
P4777
【模板】扩展中国剩余定理(exCRT)提高
6
P1516
青蛙的约会提高
7
P2480
[SDOI2010] 古代猪文提高
8
P4720
【模板】扩展卢卡斯定理/exLucas提高
9
P2183
[国家集训队] 礼物提高
10
P3306
[SDOI2013] 随机数生成器提高
11
P4774
[NOI2018] 屠龙勇士省选/NOI-
12
P4245
【模板】任意模数多项式乘法(MTT)省选/NOI-

提示:第 1、2、6 题是上一讲 exgcd 的复习;第 3~5 题是本讲的核心模板;第 7、8、9 题需要"拆模数 + 卢卡斯定理 + CRT"组合出招;第 11 题是 exCRT 在真实场景中的经典应用(每个方程两边的系数还要先用扩展欧几里得处理);第 12 题则是 CRT 在多项式领域的最硬核应用 —— 建议按顺序推进,学完 s6-08(鸽巢原理与二项式定理)后再回头挑战第 7 ~ 9 题会更顺畅。