引入
看了洛谷的一些题解,很多引用了百度百科的一些官方求法,很复杂,虽然正确,但是对于初学者来说不是特别好懂。
于是写一篇来介绍一下这个定理吧,概念更清晰一些。
在说中国剩余定理之前 首先先介绍 裴蜀定理
对于任意的正整数a,b,一定存在非零整数x,y,使得ax + by = gcd(a,b)
这里不做详细证明,大家可以思考一下如何证明?
其实简单来理解就是,因为a和b一定是最大公约数的倍数,所以a和b的倍数(非零整数倍)
也一定是最大公约数的倍数,那么我们只要让这个倍数等于1,就能找到一组解x,y满足上述方程;
那么对于上个式子,如何来求出解呢?
这里就用到了我们的扩展欧几里得算法,如果大家不会的话,可以学习一下,或者我将在后面的博客中补充
题目
题目链接:洛谷P1495
题目的大意就是这幅图 让我们求出满足方程的x的最小非负整数解
对于这样的一个线性同余方程组 我们可以先考虑 假设只有两个方程的方程组的情况
首先我们以这幅图中第一个方程与第二个方程为例
那么可以知道
x = k1* a1 + m1;①
x = k2 * a2 + m2;②
我们假设k1,k2∈Z;
那么联立两个方程 就可以得到
k1 * a1 + m1 = k2 * a2 + m2;
移项 得
k1* a1 - k2 * a2 = m2 - m1;
乍一看 天哪 这不是和扩展欧几里得算法中 求 ax + by = d的一组解一模一样(d指的是 gcd(a,b)
这个方程有解情况的充要条件是 gcd(a1,a2) | (m2 - m1);
所以我们同样可以用扩展欧几里得算法 求出 k1 * a1 - k2* a2 = d的一组解k1,k2(注意这里的d不是指m2 - m1,
而是gcd(a1,a2);
可是原来的方程右侧是m2-m1啊,不慌,认真思考下,a1与a2都是常数,我们只要变化k1,把它乘一个(m2-m1)/d不就可以了。
由此我们得到了该同余方程的一组解k1,k2
细心的我们还可以推出 这个方程k1的所有解应该是k1 = k1+k * (a2/d)
代入①式可以得到
x = k1*a1 + m1
= (k1+ k * (a2/d))a1 + m1
=a1k1+ m1 +k * (a1 * a2)/d
我们发现a1*a2/d事实上就是a1与a2的最小公倍数,可以把它记为a
那么上面这个式子就可以化简为
x = a1k1 + m1 + k* a
又∵a1k1 +m1 事实上是我们方程组的一个解x
我们可以把它记为x0;
那么上面的式子就变成了 x = k * a + x0;发现了吗,这个式子与我们前面的①式极为相似;
于是我们通过上面的计算 将两个方程联立后成功化简成了一个方程,并且求出了一组k,a以及我们的解m(即为x);
用上面的思路,每次将两个方程合并为一个方程,更新我们的解,由于有n个方程,总共迭代n - 1次,最终得到我们的答案
注意,我们每次更新答案时,因为都要求出最小非负整数解,所以在不断取模的时候要注意让余数不能为负数,所以要取模加模再取模;
这样我们就解决了这样一道水题————
#include<iostream> #include<cstring> #include<algorithm> using namespace std; typedef long long LL; LL exgcd(LL a,LL b,LL &x,LL &y) { if(!b) { x = 1,y = 0; return a; } LL d = exgcd(b,a % b,y,x); y -= a / b * x; return d; }//扩展欧几里得算法 int main() { int n; cin>>n; LL a1,m1; cin>>a1>>m1; bool flag = true; for(int i = 0; i < n - 1; ++ i) { LL a2,m2; cin>>a2>>m2; LL k1,k2; LL d = exgcd(a1,a2,k1,k2);//求一下右边等式在最大公约数的条件下的一组解 if((m2 - m1) % d) { flag = false; break; }//判断是否有解 k1 *= (m2 - m1)/d; LL t = a2/d; k1 = ((k1 % t) + t) % t; m1 = a1 * k1 + m1; a1 =abs(a1 / d * a2);//依次更新我们的m1,k1,a1;这里最小公倍数我们默认为正数(实际上取负数也可以) } if(flag) { cout<<((m1 % a1) +a1) % a1<<endl;//别忘了我们要最优解) } else puts("-1"); return 0; }