中国剩余定理(Chinese remainder theorem,简称CRT)即孙子定理,最早可见于中国南北朝时期(公元5世纪)的数学著作《孙子算经》卷下第二十六题,叫做“物不知数”问题,原文如下:
有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?——《孙子算经》
翻译成数学式子大概就是
x ≡ 2 ( m o d 3 ) x \equiv 2(mod 3) x≡2(mod3)
x ≡ 3 ( m o d 5 ) x \equiv 3(mod 5) x≡3(mod5)
x ≡ 2 ( m o d 7 ) x \equiv 2(mod7) x≡2(mod7),求解 x x x
对于《孙子算经》中提出的问题,具体解法大概分三步:
找出三个数,从 3 3 3和 5 5 5的公倍数中找出被 7 7 7除余 1 1 1的最小数 15 15 15 ( 15 ≡ 1 m o d 7 ) (15 \equiv 1 mod 7) (15≡1mod7)
从 3 3 3和7的公倍数中找出被 5 5 5除余1的最小数 21 21 21 ( 21 ≡ 1 m o d 5 ) (21 \equiv 1 mod 5) (21≡1mod5)
最后从 5 5 5和 7 7 7中找出除 3 3 3余 1 1 1的最小数 70 70 70 ( 70 ≡ 1 m o d 3 ) (70 \equiv 1 mod 3) (70≡1mod3)
用 15 15 15乘 2 2 2( 2 2 2为最终结果除以 7 7 7的余数), 21 21 21乘 3 3 3( 3 3 3为最终结果除以 5 5 5的余数), 70 70 70乘 2 2 2( 2 2 2为最终结果除以 3 3 3的余数),把三个乘积相加 ( 15 × 2 + 21 × 3 + 70 × 2 ) (15 \times 2+21 \times 3+70 \times 2) (15×2+21×3+70×2)得到 233 233 233.
用 233 233 233除以 3 , 5 , 7 3,5,7 3,5,7的最小公倍数 105 105 105,得到余数 23 23 23,即 233 ≡ 23 ( m o d 105 ) 233 \equiv 23(mod 105) 233≡23(mod105),这个 23 23 23就是符合条件的最小数
上面提到的解法用到了一个取模的性质
取模定理:两数不能整除,若被除数扩大(或缩小)了几倍,而除数不变,则其商和余数也同时扩大(或缩小)相同的倍数(余数扩大后仍然小于除数)。
即,如果a % b = c,那么如果 x % b = c × 2,此时有 x = a × 2;
关于中国剩余定理的其他详细情况可以去问度娘,不在此赘述友情链接
在这里给出两篇写的还不错的博客,作为补充:
中国剩余定理详解1(!!!下面绿色的图来自这篇链接博客)
中国剩余定理详解2(推理过程很详细)
把问题一般化,即给出一元线性同余方程组(形式如下所示),并求解满足条件的最小非负 x x x(很显然, x x x有无穷多个)
x ≡ a 1 ( m o d m 1 ) x \equiv a_1(mod m_1) x≡a1(modm1)
x ≡ a 2 ( m o d m 2 ) x \equiv a_2(mod m_2) x≡a2(modm2)
. . . ... ...
x ≡ a n ( m o d m n ) x \equiv a_n(mod m_n) x≡an(modmn)
假定 m m m两两互质
在模数互质的情况下, x x x是一定有解的(稍微想一想就明白了)
令 M = ∏ m i M = \prod m_i M=∏mi, M i = M / m i M_i = M / m_i Mi=M/mi, t i t_i ti 为 M i M_i Mi 模 m i m_i mi 意义下的逆元
那么,其中一定有一个解为:
x 0 = ∑ a i ∗ t i ∗ M i x_0=\sum a_i*t_i*M_i x0=∑ai∗ti∗Mi
通解为: x = x 0 + k ∗ M x = x_0 + k * M x=x0+k∗M
最小解为: x = ( x 0 x = (x_0 x=(x0% M + M ) M+M) M+M)% M M M
模数互质的情况比较简单,直接上马
给个板题:[洛谷P1495]曹冲养猪
#include
#include
using namespace std;
#define LL __int128 //long long会爆, 在下面的代码里要注意各个函数传参的类型
const int N = 10;
int n, m[N + 5], r[N + 5];
void exgcd (const LL a, const LL b, int &x, int &y) {
if (!b) {
x = 1;
y = 0;
return ;
}
exgcd (b, a % b, y, x);
y -= (a / b) * x;
}
LL CRT () {
LL ans = 0, lcm = 1;
int x, y;
for (int i = 1; i <= n; ++ i)
lcm *= m[i];
for (int i = 1; i <= n; ++ i) {
LL t = lcm / m[i];
exgcd (t, m[i], x, y);
x = (x % m[i] + m[i]) % m[i];
ans = (ans + t * x % lcm * r[i] % lcm) % lcm;
}
return (ans + lcm) % lcm;
}
void print (const LL x) {
if (x > 9)
print (x / 10);
putchar (x % 10 + '0');
}
int main () {
scanf ("%d", &n);
for (int i = 1; i <= n; ++ i)
scanf ("%d %d", &m[i], &r[i]);
LL ans = CRT ();
print (ans);
return 0;
}
上面讨论了模数互质的情况,下面来讨论模数不互质的情况,会比较复杂,我们采用合并方程的手段来解这种情况,首先讨论两个方程的情况(借用一位菊苣的图片,前面有复制博文链接的哦(手动跪谢)(:
对于多个方程的求解,
我们设这个 x x x为 x 0 x_0 x0,所以,可以得到的通解为 x = x 0 + k × l c m ( m 1 , m 2 ) x=x0+k \times lcm(m1,m2) x=x0+k×lcm(m1,m2)
将这个方程转化一下,可以得到一个新的同余方程
x = x 0 ( m o d x=x_0(mod x=x0(mod l c m ( m 1 , m 2 ) ) lcm(m_1,m_2)) lcm(m1,m2))
我们便成功的将两个方程转化为了一个方程
后面以此类推,得到最后一个 x 0 x_0 x0,即为我们所需要的答案。
代码取模比较多,萌新们初看可能有点懵,多想一会就懂了(本蒟蒻也被整蒙了的 )
代码给的是poj2891 Strange Way to Express Integers的Ac代码,还是比挺板的一道题,适合上手
#include
#include
using namespace std;
#define LL long long
const int N = 1e5;
int n;
LL m[N + 5], r[N + 5];
LL exgcd (const LL a, const LL b, LL &x, LL &y) {
if (!b) {
x = 1;
y = 0;
return a;
}
int d = exgcd (b, a % b, y, x);
y -= (a / b) * x;
return d;
}
LL excrt () {
LL x, y, lcm = m[1], R = r[1];
for (int i = 2; i <= n; ++ i) {
LL d = exgcd (lcm, m[i], x, y), c = R - r[i];
if (c % d)
return -1;
x = x * c / d % (m[i] / d);
R -= lcm * x;
lcm = lcm * m[i] / d;
R %= lcm;
}
return ( R + lcm ) % lcm;
}
int main () {
while ( scanf ("%d", &n) != EOF ) {
for (int i = 1; i <= n; ++ i)
scanf ("%lld %lld", &m[i], &r[i]);
LL ans = excrt ();
printf ("%lld\n", ans);
}
return 0;
}
很少写算法博客,不太会表达,以后有机会还是要来完善一下(前提是我记得),毕竟中国剩余定理在数论中还是很重要的一个版块