zoukankan      html  css  js  c++  java
  • 浅谈中国剩余定理

    引入

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

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

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

    对于任意的正整数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;
    }
  • 相关阅读:
    EMF介绍系列(一、EMF与MDA)
    EMF介绍系列(四、枚举类型、自定义类型和Map)
    使用osgi.util.NLS简化资源文件访问
    2012 定制化产品探讨(周金根).pdf
    敏捷个人理念与模型PPT及今年唯一一次的公开线上课堂
    生活:父与子三亚行
    与北邮学子交流成长,敏捷个人总体介绍 PPT
    敏捷个人教你如何制作2012生活看板
    敏捷个人架构图 V1.3
    敏捷个人微刊封面及敏捷个人使命和加入社区方式
  • 原文地址:https://www.cnblogs.com/yjyl0098/p/14439593.html
Copyright © 2011-2022 走看看