问题
孙子定理是中国古代求解一次同余式组(见同余)的方法。是数论中一个重要定理。又称中国余数定理。一元线性同余方程组问题最早可见于中国南北朝时期(公元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;
}