中国剩余定理
Posted zylak
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了中国剩余定理相关的知识,希望对你有一定的参考价值。
小学奥数中我们学过一个经典的问题:有一个数除以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,m2 ......mn任意两数互质。
那么就有:方程组 的通解形式为
下面讲一下具体的代码实现:
1. 依次考虑每个模数,对于某个模数mi,我们首先要找一个其他所有模数的公倍数x,满足题设中的x%mi=resti(resti表示题设中规定的那个余数)
2. 找最小公倍数xi的方法:因为其他所有模数任意两个都互质,所以最小公倍数就是其他所有模数的乘积。可以先预处理计算出M=(m1*m2*......mn),也就是先把所有模数都乘起来,那么xi=M/mi
3. 最小公倍数xi不一定满足xi%mi=resti,那我们就在公倍数里找,即存在(p*xi)%mi=resti,而关键就在求出p*xi是多少。
4. p*xi可以通过求解同余方程p*xi ≡ 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的一组特解,再用解得的p乘xi再乘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再除以d(exgcd求出的M和mk的最大公约数),主要因为M和mk不一定互质,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 ",work()); } return 0; }
以上是关于中国剩余定理的主要内容,如果未能解决你的问题,请参考以下文章