浅谈中国剩余定理

引入

 看了洛谷的一些题解,很多引用了百度百科的一些官方求法,很复杂,虽然正确,但是对于初学者来说不是特别好懂。

于是写一篇来介绍一下这个定理吧,概念更清晰一些。

在说中国剩余定理之前 首先先介绍 裴蜀定理

对于任意的正整数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;
}
原文地址:https://www.cnblogs.com/yjyl0098/p/14439593.html