问题
孙子定理是中国古代求解一次同余式组(见同余)的方法。是数论中一个重要定理。又称中国余数定理。一元线性同余方程组问题最早可见于中国南北朝时期(公元5世纪)的数学著作《孙子算经》卷下第二十六题,叫做“物不知数”问题,原文如下:
有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?即,一个整数除以三余二,除以五余三,除以七余二,求这个整数。《孙子算经》中首次提到了同余方程组问题,以及以上具体问题的解法,因此在中文数学文献中也会将中国剩余定理称为孙子定理。
抽象来说,就是已知m和a求解一次同余方程
{ x 三 m 1 ( m o d a 1 ) x 三 m 2 ( m o d a 2 ) . . . x 三 m n ( m o d a n ) \begin{cases} x三m_1(mod \ a_1) \\ x三m_2(mod \ a_2) \\ ...\\ x三m_n(mod \ a_n) \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧x三m1(mod a1)x三m2(mod a2)...x三mn(mod an)
思路
由于由n个同余方程,要求直接解上式较为复杂,所以先考虑只有两个同余方程的情况。
{ x 三 m 1 ( m o d a 1 ) x 三 m 2 ( m o d a 2 ) \begin{cases} x三m_1(mod \ a_1) \\ x三m_2(mod \ a_2) \\ \end{cases} {
x三m1(mod a1)x三m2(mod a2)
一般求解同余方程都要先将同余方程转换为一般方程的格式。上式可以转换为。
{ x = k 1 ∗ a 1 + m 1 x = k 2 ∗ a 2 + m 2 \begin{cases} x=k_1*a_1+m_1 \\ x=k_2 *a_2+m_2 \\ \end{cases} {
x=k1∗a1+m1x=k2∗a2+m2
可得
k 1 ∗ a 1 + m 1 = k 2 ∗ a 2 + m 2 k_1*a_1+m_1=k_2 *a_2+m_2 k1∗a1+m1=k2∗a2+m2
对方程进行变换后得
k 1 ∗ a 1 − k 2 ∗ a 2 = m 2 − m 1 k_1*a_1-k_2*a_2=m_2-m_1 k1∗a1−k2∗a2=m2−m1
其中, m 2 , m 1 , a 1 , a 2 m_2,m_1,a_1,a_2 m2,m1,a1,a2都是已知量,上式就符合 a x + b y = c ax+by=c ax+by=c的不定方程形式,可以通过扩展欧几里得算法求解。
扩展欧几里得算法解不定方程
对不定方程求解得
{ k 1 = k 01 + k ∗ a 2 d k 2 = k 02 + k ∗ a 1 d \begin{cases} k_1=k_{01}+k* \frac {a_2} d \\ k_2=k_{02}+ k*\frac {a_1} d \\ \end{cases} {
k1=k01+k∗da2k2=k02+k∗da1
其中 k 1 , k 2 k_1,k_2 k1,k2表示满足上述方程的所有解, k 0 , 1 , k 02 k_{0,1},k_{02} k0,1,k02表示满足方程的一组特解,k是任意非零整数,d是 a 1 , a 2 a_1,a_2 a1,a2的最大公约数
将 k 1 k_1 k1回代到 x = k 1 ∗ a 1 + m 1 x=k_1*a_1+m_1 x=k1∗a1+m1,得 x = a 1 ∗ k 01 + k ∗ a 2 a 1 d + m 1 x=a_1*k_{01}+k* \frac {a_2a_1} d+m_1 x=a1∗k01+k∗da2a1+m1,其中 a 2 a 1 d \frac {a_2a_1} d da2a1是 a 2 , a 1 a_2,a_1 a2,a1的最小公倍数。
于是,上述两个同余方程就被转化为了一个同余方程。
x = k ∗ a 2 a 1 d + x 0 x=k*\frac {a_2a_1} d+x_0 x=k∗da2a1+x0
即
x 三 x 0 ( m o d a 2 a 1 d ) x三x_0(mod \ \frac {a_2a_1} d) x三x0(mod da2a1)
其中, x 0 x_0 x0是满足上述同余方程的任意一组解,x是满足同余方程组的所有解。
推广
在思路中,证明了两个一阶同余方程组可以被转换为一个一阶同余方程。所以,要求解n个一阶同余方程,可以依次转换,化繁为简。
{ x 三 m 1 ( m o d a 1 ) x 三 m 2 ( m o d a 2 ) . . . x 三 m n ( m o d a n ) = { x 三 x 0 ( m o d a 2 a 1 d ) x 三 m 3 ( m o d a 3 ) . . . x 三 m n ( m o d a n ) = . . . \begin{cases} x三m_1(mod \ a_1) \\ x三m_2(mod \ a_2) \\ ...\\ x三m_n(mod \ a_n) \end{cases}= \begin{cases} x三x_0(mod \ \frac {a_2a_1} d)\\ x三m_3(mod \ a_3) \\ ...\\ x三m_n(mod \ a_n) \end{cases}=... ⎩⎪⎪⎪⎨⎪⎪⎪⎧x三m1(mod a1)x三m2(mod a2)...x三mn(mod an)=⎩⎪⎪⎪⎨⎪⎪⎪⎧x三x0(mod da2a1)x三m3(mod a3)...x三mn(mod an)=...
最终可以将上式转换为两个一阶同余方程组,利用扩展欧几里得算法就可以求得正确的解。
例题
代码模板
#include<iostream>
using namespace std;
typedef long long LL;
LL exgcd(LL a,LL b,LL &x,LL &y){
#扩展欧几里得算法
if(b==0){
x=1;
y=0;
return a;
}
int d = exgcd(b,a%b,y,x);
y-=(a/b)*x;
return d;
}
int main(){
int n;
LL a1,m1;
LL a2,m2,k1,k2;
cin>>n;
cin>>a1>>m1;
for(int i=0;i<n-1;i++){
scanf("%d%d",&a2,&m2);
//求解不定方程,默认方程右边是最大公约数
int d = exgcd(a1,a2,k1,k2);
if((m2-m1)%d!=0){
//方程无解
printf("-1");
return 0;
}
k1 *= (m2-m1)/d;//得到满足条件的一个k0
LL t = a2/d;
k1 = (k1%t+t)%t;//寻找满足条件的最小正整数k0
m1 = k1*a1+m1; //更新m1
a1 = a1/d*a2;//更新a1,先除法是为了防止越界
}
printf("%lld",m1);//输入m1
return 0;
}