zoukankan      html  css  js  c++  java
  • 【数论】C++实现中国剩余定理求一次同余方程组

    很久很久以前,《孙子算经》里,有一道“物不知数”题:

    有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?

    就是说,有一堆东西,三个三个数剩余两个,五个五个数剩余三个,七个七个数剩余两个,请问有多少个?

    粗涉数论的人应该知道“同余”和“同余式(组)”的概念。设这堆东西的数量为x,则原问题可表示为同余式组

        x ≡ 2  (mod 3),

        x ≡ 3  (mod 5),

        x ≡ 2  (mod 7).

    原问题在《孙子算经》里的解释为:“三三数之剩二,置一百四十;五五数之剩三,置六十三;七七数之剩二,置三十。并之,得二百三十三,以二百十减之,即得。答曰:二十三。”(引用自百度百科:物不知数)

    即:先求被3除余2,并能同时被5、7整除的数,这样的数最小是35;
    再求被5除余3,并能同时被3、7整除的数,这样的数最小是63;
    然后求被7除余2,并能同时被3、5整除的数,这样的数最小是30。(引用自百度百科)
    他们相加得128,但是它不是最小的。可以求它除以(3*5*7=105)的余数,也就是答案23.
    同理,所有模105余23的数都是原问题的答案:
        x ≡ 23  (mod 105).
    这种问题有一般的推广形式,就是很重要的中国剩余定理:
    设m1,m2,...,mk是k个两两互素的正整数,则对任意的整数b1,b2,...,bk,同余式组
      x ≡ b1  (mod m1),
      x ≡ b2  (mod m2),
      ......
      x ≡ bk  (mod mk)
    一定有解,且解是唯一的。
    若令
      m = m1m2...mk ,  m / mi = Mi , Mi'M≡ 1  (mod mi) 【即Mi' 是Mi的模mi逆元】
      x ≡ b1M1'M1 + b2M2'M2 + ... + bkMk'Mk  (mod m).
    利用万能的C++,我们可以设计出运用中国剩余定理求x的程序。
    首先是开头部分:
    1 #include<iostream>
    2 #include<cstdio>
    3 #include<cmath>
    4 #define MAXN 10000
    5 using namespace std;
    6 int TotalNums;//同余方程组中同余方程的数量
    7 int b[MAXN],m[MAXN],M[MAXN],revM[MAXN],kM;//b[i]是第i个同余式右边,m[i]是第i个同余式的模,kM是所有模的乘积,M[i]是kM与m[i]的商,revM[i]是M[i]模m[i]的逆元。

    对于程序,这几个函数是必要的:

    1 int gcd(int a,int b){return (a%b==0)?b:gcd(b,a%b);}//欧几里得递归求最大公约数
    2 int lcm(int a,int b){return a*b/gcd(a,b);}//根据两个数的最大公约数和最小公倍数的乘积等于这两个数的乘积,求出最小公倍数
    3 void exgcd(int,int,int&,int&);//广义欧几里得算法
    4 int getRev(int);//获得逆元,参数表示这是第几个同余式的情况。
    5 int getProduct(int[]);//数组求积
    6 bool isEachPrime();//判断数列中的数是否互素
    7 int _x[MAXN],_y[MAXN];//临时变量,供exgcd函数后两个引用参数使用
     1 void exgcd(int a,int b,int& x,int& y)//求满足xa+yb=(a,b)的整数x,y.由于不好返回两个值,因此存储在参数x和y中,因此要使用引用。
     2 {
     3     if(b==0)
     4     {
     5         x=1;y=0;
     6         return;
     7     }
     8     exgcd(b,a%b,x,y);//具体请参阅“Bezout恒等式”
     9     int p=x;
    10     x=y;
    11     y=p-a/b*y;
    12 }//其实这段代码我是背下来的,我也不太理解o(╥﹏╥)o
    13 int getRev(int i)
    14 {
    15     exgcd(M[i],m[i],_x[i],_y[i]);//对于满足xa+yb=(a,b)的同余式,如果b=1,则上述x是满足ax≡1(mod b)的解。可以证明当(a,b)=1时,这个同余式有唯一解。
    16     return (_x[i]+m[i])%m[i];//得到最小值
    17 }
    18 int getProduct(int a[])//获得数组a的元素乘积
    19 {
    20     int p=1;
    21     for(int i=1;i<=TotalNums;i++)
    22     {
    23         p*=a[i];
    24     }
    25     return p;
    26 }
    27 bool isEachPrime()//判断数组m的元素是否两两互素。如果一个数组的元素都是两两互素,那么它们的最小公倍数是它们的乘积。求数组最小公倍数的方法:先求出前两个元素的最小公倍数,再求出这个最小公倍数和第三个元素的最小公倍数,以此类推。
    28 {
    29     int p=m[1];
    30     for(int i=2;i<=TotalNums;i++)
    31     {
    32         p=lcm(m[i],p);
    33     }
    34     if(p!=getProduct(m)) return false;
    35     else return true;
    36 }

    最后是主程序:

    int main()
    {
        cout<<"请输入同余式数量:";
        cin>>TotalNums;
        cout<<"格式:"<<endl;
        for(int i=1;i<=TotalNums;i++)
        {
            cout<<"x≡b_"<<i<<"(mod m_"<<i<<")"<<endl;
        }
        for(int i=1;i<=TotalNums;i++)
        {
            cout<<"请输入b_"<<i<<":";
            cin>>b[i];
            cout<<"请输入m_"<<i<<":";
            cin>>m[i];
         b[i]%=m[i];//防止出现同余式右边比模大的情况 }
    if(!isEachPrime())//无解 { cout<<"m没有两两互素,原方程组无解。"<<endl; return 0; } else//有解 { kM=getProduct(m);//获得模的乘积 for(int i=1;i<=TotalNums;i++) { M[i]=kM/m[i];//求得M[i] revM[i]=getRev(i);//获得逆元 } int result=0; for(int i=1;i<=TotalNums;i++) { result+=(b[i]*revM[i]*M[i]);//根据中国剩余定理的定义 } result%=kM;//获得最终结果 cout<<"结果: x≡"<<result<<"(mod "<<kM<<")"; return 0; } }
     
  • 相关阅读:
    Jboss部署war以及获取Resource的真实路径
    命令行获取docker远程仓库镜像列表
    Hibernate5 与 Spring Boot2 最佳性能实践
    Spring Bean的一生
    Spring中统一相同版本的api请求路径的一些思考
    Java并发工具类CountDownLatch源码中的例子
    (转载)23种设计模式的uml图表示及通俗介绍
    GeoHash核心原理解析
    如何保证服务器的安全?
    小强升职记
  • 原文地址:https://www.cnblogs.com/jiangyuechen/p/12502764.html
Copyright © 2011-2022 走看看