中国剩余定理

小学奥数中我们学过一个经典的问题:有一个数除以3余2,除以5余3,除以7余5,求这个数。 这就是著名的中国剩余定理

 

中国剩余定理:

  我们就先来考虑上述的这个问题。不妨先设三个整数n1,n2,n3满足:n1%3=2,n2%5=2,n3%7=2。接下来,对于n1而言,如果使得n1满足n1%5=0并且n1%7=0,那么(n1+n2)%5=n2%5=2并且(n1+n3)%7=2

这个不难说明,因为(n1+n2)%5=((n1%5)+(n2%5))%5,因为我们使得n1%5=0,所以((n1%5)+(n2%5))%5=(0+(n2%5))%5。至此,我们不难发现如果使得n2,n3有类似的性质(即:n2%3=0并且n2%7=0,n3%3=0并且n3%5=0),那么(n1+n2+n3就会同时满足题设中三个要求。这样看来,我们要求的就是n1,n2,n3这样的数,并把它们相加。整理一下上述n1,n2,n3满足的条件即:(以n1为例)

1. n1%3=2(找一个数满足题设中的某一个条件)

2. n1%5=0并且n1%7=0(找到的这个数同时也是其它所有数的公倍数)

值得注意的是,题目中的任意两个模数都是互质的,所以要求几个模数的最小公倍数的时候,只需要将它们相乘即可。

综上所述,我们可以将这个简单的问题变成一个求解数学问题的通法:

  求出一个整数x满足: ,其中m1,m......mn任意两数互质。

那么就有:方程组  的通解形式为

 

 

下面讲一下具体的代码实现:

1. 依次考虑每个模数,对于某个模数mi,我们首先要找一个其他所有模数的公倍数x,满足题设中的x%mi=restiresti表示题设中规定的那个余数)

2. 找最小公倍数xi的方法:因为其他所有模数任意两个都互质,所以最小公倍数就是其他所有模数的乘积。可以先预处理计算出M=(m1*m2*......mn,也就是先把所有模数都乘起来,那么xi=M/mi

3. 最小公倍数xi不一定满足xi%mi=resti,那我们就在公倍数里找,即存在(p*xi)%mi=resti,而关键就在求出p*xi是多少。

4. p*xi可以通过求解同余方程p*x≡ resti(mod mi求解。根据求解线性同余方程的方法,我们设p*xi-resti=(-q)*mi 然后exgcd求解即可。

5. 求解exgcd具体细节:写成标准一次式:xi*p+mi*q=resti,该等式一定有解,因为xi=M/mi且任意两个模数互质,所以gcd(xi,mi)=1,所以该方程有解。根据exgcd,我们先求出了xi*p+mi*q=gcd(xi,mi)=1的一组特解,再用解得的pxi再乘resti即为在xi*p+mi*q=resti方程成立情况下的p*xi,这就是我们要求的那个公倍数。

6. 按照上面的步骤,我们求出了一个满足题设某一条件的整数,然后继续考虑下一个条件。步骤相同,只需把每一步求出的整数加起来就是最后结果。

7. 如果题目是让我们求出满足条件的最小正整数,我们则需要对于每步的答案%M即可。

具体代码如下:

#include<cstdio>
int n,m[101],rest[101],M=1; 
int exgcd(int xi,int mi,int &p,int &q)//扩展欧几里得算法 
{
    if (mi==0) {p=1; q=0; return xi;}
    int d=exgcd(mi,xi%mi,p,q);
    int z=p; p=q; q=z-(xi/mi)*q;
    return d;
}
int CR()//中国剩余定理 
{
    int sum=0,p,q;
    for (int i=1;i<=n;i++)//依次考虑每一个题设 
    {
        int xi=M/m[i];//求出其余模数的最小公倍数 x
        exgcd(m[i],xi,q,p);//求解:xi*p+mi*q=gcd(xi,mi)=1
        sum=(sum+xi*p*rest[i])%M;//确保数最小 
    }
    return (sum%M+M)%M;
}
int main()
{
    scanf ("%d",&n);//输入n个题设条件
    for (int i=1;i<=n;i++)
    {
        scanf ("%d%d",&m[i],&rest[i]);//输入n个模数和余数 
        M*=m[i];//提前计算所以模数的积,为后面求最小公倍数做准备 
    }
    printf("%d",CR());
    return 0;
 } 

 

中国剩余定理针对于任意两个模数都互质,那么对于不都互质的同余方程组,也有求解方法:  //POJ 2891 

题目:给定2n个整数:a1,a2 ...... an , m1,m2 ...... mn ,求一个最小正整数,满足:对于1<=i<=n,x≡ai(mod mi),或给出无解。

思路:因为不满足模数两两互质,中国剩余定理不再适用,我们考虑归纳法:假设前k-1个方程已经求出解x,M是前k-1个模数的乘积。那么前k-1个方程的通解就是x+i*M。接下来我们考虑第k个方程:求出一个t使得:x+t*M≡ak(mod mk),t*M≡ak-x(mod mk,然后就是exgcd求解这个同余方程,有解则满足(ak-x)% gcd(t*M,mk)== 0,否则输出-1无解。并且更新前k个方程的解,即为:x'=x+t*M 。 这里我们需要弄清楚M的作用,即是前面所有模数的公倍数,使得我们当前枚举的这个数不会对前面的条件产生影响。所以在具体操作来更新M的时候,我们要保证求出的整数最小的要求Mk-1更新到Mk时,我们不是直接*mk,而是乘mk再除以dexgcd求出的Mmk的最大公约数),主要因为Mmk不一定互质,M中可能有mk的因子,所以把它们共同因子除去不会影响结果,即:保证了M的最小性和不会对前面的条件产生影响的性质。另外,x+t*M≡ak(mod mk这个式子也就告诉我们,t<=mk,所以对于求解出来的t要对mk取模,保证最小。

代码如下:

#include<cstdio>
typedef long long ll;
ll rest[20005],a[20005];
ll X,M,n;
ll exgcd(int a,int b,int &x,int &y)
{
    if (b==0) {x=1; y=0; return a;}
    ll d=exgcd(b,a%b,x,y);
    ll z=x; x=y; y=z-y*(a/b);
    return d;
}
ll work()
{
    M=a[1],X=rest[1];
    int x,y;
    for (int i=2;i<=n;i++)
    {
        ll d=exgcd(M,a[i],x,y);
        if ((rest[i]-X)%d!=0) return -1;
        x=(rest[i]-X)/d*x%a[i];
        X+=(x*M);
        M=M/d*a[i];
        X%=M;
    }
    return (X%M+M)%M;
}
int main()
{
    while (scanf ("%lld",&n)!=EOF)
    {
        for (int i=1;i<=n;i++)
              scanf ("%lld%lld",&a[i],&rest[i]);
        printf("%lld\n",work());
    }
    return 0;
 } 

 

posted @ 2018-09-07 19:16  zylAK  阅读(736)  评论(0编辑  收藏  举报