CRT中国剩余定理
来源
中国剩余定理其实并不神秘,它在中国古代有一个非常接地气的名字,叫做“韩信点兵”。第一步:直觉与物理意义 —— 韩信点兵传说韩信带兵,他不直接去数有多少人,而是让士兵排阵:士兵 3 人一排,多出 2 人。士兵 5 人一排,多出 3 人。士兵 7 人一排,多出 2 人。韩信看了一眼,就知道了总人数。用数学语言翻译一下,就是要求一个未知的整数 \(x\),满足这样一个一元线性同余方程组:$$x \equiv 2 \pmod 3$$$$x \equiv 3 \pmod 5$$$$x \equiv 2 \pmod 7$$如果去死猜这个数字会很麻烦。中国剩余定理的核心智慧,在于“分而治之,互不干扰”的构造思想。
核心构造思想
怎样才能找出一个 \(x\),能同时满足这三个条件?CRT 的思路是:我们把 \(x\) 拆分成三个部分相加:
\(x = x_1 + x_2 + x_3\)。我们希望这三个部分各司其职:
\(x_1\) 专门负责搞定第一个条件(除以 3 余 2)。同时,为了不干扰别人,它必须是 5 和 7 的倍数(这样除以 5 和 7 余数都是 0)。
\(x_2\) 专门负责搞定第二个条件(除以 5 余 3)。同时,它必须是 3 和 7 的倍数。
\(x_3\) 专门负责搞定第三个条件(除以 7 余 2)。同时,它必须是 3 和 5 的倍数。
如果你能构造出这样的 \(x_1, x_2, x_3\),把它们加起来,完美的 \(x\) 就诞生了!
怎么构造
我们以构造 \(x_1\) 为例。已知 \(x_1\) 必须是 5 和 7 的倍数,也就是 \(35\) 的倍数。我们要找一个 \(35\) 的倍数,让它除以 3 余 2。怎么找呢?数学家喜欢先找“单位 1”:能不能先找一个 35 的倍数,让它除以 3 余 1?设这个数是 \(35 \cdot t_1\)。我们需要:$$35 \cdot t_1 \equiv 1 \pmod 3$$发现了吗?!这不就是求 \(35\) 在模 \(3\) 意义下的乘法逆元吗?这就是我们刚刚用扩展欧几里得算法学的绝技!调用一下我们刚写的函数 exgcd(35, 3, t1, y),求出来的 \(t_1\) 就是我们要找的系数(这里 \(t_1 = 2\))。既然 \(35 \cdot 2 \equiv 1 \pmod 3\)(70 除以 3 确实余 1)。我们要的是余 2,所以只要在这个“单位 1”的基础乘上韩信要求的余数 \(2\) 即可:$$x_1 = 2 \cdot (35 \cdot 2) = 140$$完美!\(140\) 是 5 和 7 的倍数,且除以 3 刚好余 2。
通用公式
把上面这个极其优雅的过程公式化。假设方程组为 \(x \equiv a_i \pmod{m_i}\)(注意:所有 \(m_i\) 必须两两互质)。计算所有模数的乘积:\(M = m_1 \cdot m_2 \cdot \dots \cdot m_k\)。对于第 \(i\) 个方程:计算除了 \(m_i\) 以外其他所有模数的乘积:\(M_i = \frac{M}{m_i}\)。用 ExGCD 求出 \(M_i\) 在模 \(m_i\) 下的乘法逆元,记作 \(t_i\)。构造出它的专属项:\(x_i = a_i \cdot M_i \cdot t_i\)。把所有的项加起来,并对总乘积 \(M\) 取模,得到最小正整数解:$$x = (x_1 + x_2 + \dots + x_k) \bmod M = \left( \sum_{i=1}^{k} a_i \cdot M_i \cdot t_i \right) \bmod M$$
模板题
P1495 【模板】中国剩余定理(CRT)/ 曹冲养猪
题目描述
自从曹冲搞定了大象以后,曹操就开始琢磨让儿子干些事业,于是派他到中原养猪场养猪,可是曹冲满不高兴,于是在工作中马马虎虎,有一次曹操想知道母猪的数量,于是曹冲想狠狠耍曹操一把。举个例子,假如有 \(16\) 头母猪,如果建了 \(3\) 个猪圈,剩下 \(1\) 头猪就没有地方安家了。如果建造了 \(5\) 个猪圈,但是仍然有 \(1\) 头猪没有地方去,然后如果建造了 \(7\) 个猪圈,还有 \(2\) 头没有地方去。你作为曹总的私人秘书理所当然要将准确的猪数报给曹总,你该怎么办?
输入格式
第一行包含一个整数 \(n\) —— 建立猪圈的次数,接下来 \(n\) 行,每行两个整数 \(a_i, b_i\),表示建立了 \(a_i\) 个猪圈,有 \(b_i\) 头猪没有去处。你可以假定 \(a_1 \sim a_n\) 互质。
输出格式
输出包含一个自然数,即为曹冲至少养母猪的数目。
输入输出样例 #1
输入 #1
3
3 1
5 1
7 2
输出 #1
16
说明/提示
\(1 \leq n\le10\),\(0 \leq b_i\lt a_i\le100000\),\(1 \leq \prod a_i \leq 10^{18}\)
通过代码
#include <iostream>
#include <algorithm>
using namespace std;
using i64 = long long;
i64 t;
i64 a[100005], b[100005];
i64 exgcd(i64 a, i64 b, i64& x, i64& y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
i64 x1, y1;
i64 d = exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
return d;
}
int main() {
cin >> t;
i64 ans = 1;
for (i64 i = 1; i <= t; i++) {
cin >> a[i] >> b[i]; // 注意题意:确保 a[i] 是模数,b[i] 是余数
ans *= a[i];
}
i64 s = 0;
for (i64 i = 1; i <= t; i++) {
i64 k = ans / a[i];
i64 x, y;
exgcd(k, a[i], x, y);
// 修复 1:将逆元强制转化为正数
x = (x % a[i] + a[i]) % a[i];
// 修复 2:使用 __int128 防止连乘时爆 long long,步步取模
// 写法解析:先将 k 转为 128位整数,乘 b[i] 取模,再乘 x 取模
i64 term = (i64)((__int128)k * b[i] % ans * x % ans);
s = (s + term) % ans;
}
// 因为前面的计算都保证了正数,这里其实直接 cout << s << endl; 也可以
// 但加上 (s + ans) % ans 是个很好的托底习惯
cout << (s + ans) % ans << endl;
return 0;
}

浙公网安备 33010602011771号