【数论基础】中国剩余定理及其扩展
先讲讲中国剩余定理
孙子定理是中国古代求解一次同余式组的方法。是数论中一个重要定理。又称中国余数定理。一元线性同余方程组问题最早可见于中国南北朝时期(公元5世纪)的数学著作《孙子算经》卷下第二十六题,叫做“物不知数”问题,原文如下:
有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?即,一个整数除以三余二,除以五余三,除以七余二,求这个整数。《孙子算经》中首次提到了同余方程组问题,以及以上具体问题的解法,因此在中文数学文献中也会将中国剩余定理称为孙子定理。
上面是度娘的。将上面的例题转化成方程组是这样的:
假设n1,n2,n3分别是满足方程①,②,③的x的解,
如果有a%b=c,则有(a+k∗b)%b=c(k为非零整数) ,从n1角度考虑,当n2和n3都是3的整数倍时,n1+n2和n1+n2+n3都能够满足除以3余2。再从n2,n3角度考虑,得到:
为使n1+n2+n3的和满足除以3余2,n2和n3必须是3的倍数。
为使n1+n2+n3的和满足除以5余3,n1和n3必须是5的倍数。
为使n1+n2+n3的和满足除以7余2,n1和n2必须是7的倍数。
为使n1+n2+n3的和作为“孙子问题”的一个最终解,需满足:
n1除以3余2,且是5和7的公倍数。
n2除以5余3,且是3和7的公倍数。
n3除以7余2,且是3和5的公倍数。
因为如果a%b=c,那么(a∗k)%b=a%b+a%b+…+a%b=c+c+…+c=k∗c(k>0)。所有在求n1时,先找一个除以3余1的数,再乘以2。也就是先求出5和7的公倍数模3下的逆元,再用逆元去乘余数。n2与n3同理。
n1+n2+n3只是问题的一个解,并不是最小的解 ,最小解是(n1+n2+n3)%105 。
由上,可以推出中国剩余定理的通用算法:
设正整数m1,m2,m3…mk两两互素(注意这里),则同余方程组
有整数解。并且在模M= m1m2m3…mk下有唯一解x为:
其中,Mi=M/mi,Mi-1为Mi模mi的逆元。
例题代码:
#include<cstdio> #include<iostream> #include<cctype> #include<algorithm> #include<cmath> using namespace std; #define int long long int n,x,y; int m[15],r[15]; inline void ex_gcd(int a,int b){ if(!b){x=1,y=0;return ;} ex_gcd(b,a%b); int t=x;x=y;y=t-a/b*y; } inline int CRT(int n,int m[],int r[]){ int M=1,t=0; for(int i=1;i<=n;i++) M*=m[i]; for(int i=1;i<=n;i++){ int w=M/m[i]; ex_gcd(w,m[i]); t=(t+w*r[i]*x)%M; } return (t+M)%M; } signed main(void){ cin>>n; for(int i=1;i<=n;i++) cin>>m[i]>>r[i]; cout<<CRT(n,m,r)<<endl; return 0; }
—————————————————————分割线————————————————————————————————
上面提到,中国剩余定理只能用在模数互质时,那不互质时?
我们需要扩展它:
同样的题,设正整数m1,m2,m3…mk,两两之间不一定互质,则有同余方程组
先从两个方程分析:
很显然,他们可以变成如下的不定方程:
变化一下:
(这里k2的正负性是无所谓的,因为待会要求的是k1)
然后我们这里用扩展欧几里得算法求出x的一个特解x0,得出以下几个式子并得出结论:
那么根据最后一个方程,可以得出思路,便是相邻两个m去进行处理,最后递推得出答案。
#include<cstdio> #include<iostream> #include<cctype> #include<algorithm> #include<cmath> using namespace std; #define int long long inline int read(){ int s=0;bool flag=true;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')flag=false;ch=getchar();} while(isdigit(ch)){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return flag?s:-s; } inline void out_put(int x){ if(x<0) x=-x,putchar('-'); if(x>9) out_put(x/10); putchar(x%10+'0'); } inline void print(int x){out_put(x),puts("");} const int N=1e5+10; int x,y; int n,mod[N],rem[N]; inline int slow_mult(int x,int y,int MOD){ x=x%MOD,y=y%MOD; return ((x*y-(int)(((long double)x*y+0.5)/MOD)*MOD)%MOD+MOD)%MOD; } inline int ex_gcd(int a,int b){ if(!b){x=1,y=0;return a;} int ans=ex_gcd(b,a%b); int t=x;x=y;y=t-a/b*y; return ans; } inline int ex_CRT(){ for(int i=1;i<n;i++){ int a=mod[i],b=mod[i+1],c=rem[i+1]-rem[i]; int gcd=ex_gcd(a,b); // if(c%gcd) return -1; //若c不能被gcd整除则无解 ,但这题保证有解 a/=gcd,b/=gcd,c/=gcd;//除去公因数,保证三者互质 x=(slow_mult(x,c,b)+b)%b; mod[i+1]=mod[i]/gcd*mod[i+1];//下一次的mod是最小公倍数 rem[i+1]=((slow_mult(x,mod[i],mod[i+1])+rem[i])%mod[i+1]+mod[i+1])%mod[i+1]; //下次的rem[i]是m[i]*x+rem[i] } return rem[n];//结果递推到了最后一位 } signed main(void){ n=read(); for(int i=1;i<=n;i++) mod[i]=read(),rem[i]=read(); cout<<ex_CRT()<<endl; return 0; }
具体递推过程可以手推一下