在介绍扩展中国剩余定理之前,让我们先介绍一下中国剩余定理。
中国剩余定理
中国剩余定理是用来求解一堆线性同余方程组的通解的算法。
该算法的时间复杂度是
O
(
方程的个数
)
O(方程的个数)
O(方程的个数)。
{
x
≡
a
1
(
m
o
d
m
1
)
x
≡
a
2
(
m
o
d
m
2
)
x
≡
a
3
(
m
o
d
m
3
)
.
.
.
.
.
.
.
.
.
x
≡
a
n
(
m
o
d
m
n
)
\left\{ \begin{array}{rcl} x\equiv a_1 & {(mod\ m_1)}\\ x\equiv a_2 & {(mod\ m_2)}\\ x\equiv a_3 & {(mod\ m_3)}\\ {... ......}\\ x\equiv a_n & {(mod\ m_n)} \end{array} \right.
⎩
⎨
⎧x≡a1x≡a2x≡a3.........x≡an(mod m1)(mod m2)(mod m3)(mod mn)
其中,
m
1
,
m
2
,
.
.
.
,
m
n
m_1,m_2,...,m_n
m1,m2,...,mn两两互质。
对于这个方程,我们很容易构造出其通解。
1.计算模数累积,令
M
=
∏
i
=
1
n
M= \prod_{i=1}^{n}
M=∏i=1n
2.枚举
i
i
i从
1
1
1到
n
n
n,将
a
i
⋅
M
m
i
⋅
i
n
v
(
M
m
i
,
m
i
)
a_i\cdot\dfrac{M}{m_i}\cdot inv \left( \dfrac{M}{m_i},m_i\right)
ai⋅miM⋅inv(miM,mi)累加到答案中,注意边累加边
m
o
d
M
modM
modM。
其中,
i
n
v
(
a
,
b
)
inv\left(a,b\right)
inv(a,b)表示
a
m
o
d
b
a \mod b
amodb的乘法逆元,可以用
e
x
g
c
d
exgcd
exgcd求出。
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;
const double eps = 1e-10;
const double pi = acos(-1.0);
int n;
ll a[30],m[30],M = 1;
/*
算法步骤:
计算模数积,然后遍历每个方程,让答案累加a[i]*M/m[i]*inv(M/m[i],m[i]);
其中inv(a,b)表示a在modb意义下的逆元。
注意,中国剩余定理必须要满足模数两两互质。
*/
template<class T> T gcd(T a, T b){
return !b?a:gcd(b,a%b);
}
void exgcd(ll a, ll b, ll& x, ll& y){
if(!b){
x = 1, y = 0;
return;
}
exgcd(b,a%b,y,x);
y -= a/b*x;
}
ll inv(ll a, ll b){
ll x,y;
exgcd(a,b,x,y);
return (x%b+b)%b;
}
void solve(){
scanf("%d",&n);
for(int i = 1; i <= n; i++){
scanf("%lld%lld",&m[i],&a[i]);
M *= m[i];
}
ll ans = 0;
for(int i = 1; i <= n; i++){
ans = (ans + a[i]*M/m[i]*inv(M/m[i],m[i]))%M;
}
printf("%lld\n",(ans%M + M)%M);
}
int main()
{
solve();
return 0;
}
扩展中国剩余定理
{
x
≡
a
1
(
m
o
d
m
1
)
x
≡
a
2
(
m
o
d
m
2
)
x
≡
a
3
(
m
o
d
m
3
)
.
.
.
.
.
.
.
.
.
x
≡
a
n
(
m
o
d
m
n
)
\left\{ \begin{array}{rcl} x\equiv a_1 & {(mod\ m_1)}\\ x\equiv a_2 & {(mod\ m_2)}\\ x\equiv a_3 &{(mod\ m_3)}\\ {... ......}\\ x\equiv a_n & {(mod\ m_n)} \end{array} \right.
⎩
⎨
⎧x≡a1x≡a2x≡a3.........x≡an(mod m1)(mod m2)(mod m3)(mod mn)
这时的方程模数不再满足两两互质,甚至还有可能无解。
还是老样子,我们先考虑简单情况。
先看前两个方程组:
{
x
≡
a
1
(
m
o
d
m
1
)
x
≡
a
2
(
m
o
d
m
2
)
\left\{ \begin{array}{rcl} x\equiv a_1 &{( mod\ m_1)}\\ x\equiv a_2 &{(mod\ m_2)}\\ \end{array}\right.
{x≡a1x≡a2(mod m1)(mod m2)
它等价于
{
x
=
a
1
+
k
1
m
1
x
=
a
2
+
k
2
m
2
\left\{ \begin{array}{rcl} x= a_1+k_1m_1\\ x= a_2+k_2m_2\\ \end{array}\right.
{x=a1+k1m1x=a2+k2m2
于是消去
x
x
x,我们就得到了一个方程:
k
1
m
1
−
k
2
m
2
=
a
2
−
a
1
k_1m_1-k_2m_2 = a_2-a_1
k1m1−k2m2=a2−a1
显然,这是个简单的二元不定方程。
如果
g
c
d
(
m
1
,
m
2
)
∤
a
2
−
a
1
gcd(m1,m2)\nmid a_2-a_1
gcd(m1,m2)∤a2−a1,那么方程组无解。
否则我们可以用
e
x
g
c
d
exgcd
exgcd求出一组解。
设这组解为
k
1
∗
,
k
2
∗
k_1^{*},k_2^{*}
k1∗,k2∗,那么我们可以得到这组方程组的通解:
{
k
1
=
k
1
∗
+
m
2
g
c
d
(
m
1
,
m
2
)
t
k
2
=
k
2
∗
+
m
1
g
c
d
(
m
1
,
m
2
)
t
\left\{ \begin{array}{rcl} k_1= k_1^{*}+\dfrac{m_2}{gcd(m_1,m_2)}t\\ \\ k_2= k_2^{*}+\dfrac{m_1}{gcd(m_1,m_2)}t\\ \end{array}\right.
⎩
⎨
⎧k1=k1∗+gcd(m1,m2)m2tk2=k2∗+gcd(m1,m2)m1t
将第一个方程带入,得
x
=
a
1
+
k
1
∗
m
1
+
m
1
m
2
d
t
=
a
1
+
k
1
∗
m
1
+
t
[
m
1
,
m
2
]
\begin{aligned} x=&{a_1+k_1^{*}m_1+\frac{m_1m_2}{d}t}\\ =&{a_1+k_1^{*}m_1+t[m_1,m_2]} \end{aligned}
x==a1+k1∗m1+dm1m2ta1+k1∗m1+t[m1,m2]
令
x
0
=
a
1
+
k
1
∗
m
1
x_0=a_1+k_1^{*}m_1
x0=a1+k1∗m1
那么原来的两个方程可以转化为一个方程
x
=
x
0
+
t
[
m
1
,
m
2
]
x=x_{0}+t[m_1,m_2]
x=x0+t[m1,m2]
这样,我们可以通过n-1次循环将n个方程合并为1个方程,最后通过解最后一个方程得到整个方程组的解。
算法步骤:
1.初始化
a
1
a_1
a1,
m
1
m_1
m1;
2.对于第
i
i
i个方程,令
a
2
=
a
i
a_2 = a_i
a2=ai,
m
2
=
m
i
m_2 = m_i
m2=mi,对其用拓展欧几里得算法:
d
=
e
x
g
c
d
(
m
1
,
m
2
,
k
1
,
k
2
)
d = exgcd(m_1,m_2,k_1,k_2)
d=exgcd(m1,m2,k1,k2)
如果
d
∤
(
a
2
−
a
1
)
d\nmid (a_2-a_1)
d∤(a2−a1)那么无解;
否则
k
1
∗
=
a
2
−
a
1
d
k_1 *= \dfrac{a_2-a_1}{d}
k1∗=da2−a1
这里我们只需要
k
1
k_1
k1的值,故不用对
k
2
k_2
k2进行任何操作。
4.为了不让结果溢出且恒为正,我们需要做一些预处理:令
t
=
m
2
d
t = \dfrac{m_2}{d}
t=dm2,然后
k
1
=
(
k
1
%
t
+
t
)
%
t
k_1 = (k_1\%t+t)\%t
k1=(k1%t+t)%t
令
a
1
=
a
1
+
k
1
m
1
,
m
1
=
∣
l
c
m
(
m
1
,
m
2
)
∣
a_1=a_1+k_1m_1,m_1=\left|lcm(m_1,m_2)\right|
a1=a1+k1m1,m1=∣lcm(m1,m2)∣。
重复循环
n
−
1
n-1
n−1次,我们可以得到一个方程
x
=
a
1
+
t
m
1
x = a_1 + tm_1
x=a1+tm1
所得结果就是
(
a
1
%
m
1
+
m
1
)
%
m
1
\left(a_1\%m_1+m_1\right)\%m_1
(a1%m1+m1)%m1。
各个变量关系非常复杂,建议在考场上实现代码的是由用手推一下式子。
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;
const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 1e6 + 10;
int n;
inline ll mul(ll a,ll b,ll mod){//O(1)取模快速乘,不会爆long long
return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;
}
ll exgcd(ll a, ll b, ll& x, ll& y){
if(!b){
x = 1, y = 0;
return a;
}
ll d = exgcd(b,a%b,y,x);
y -= a/b*x;
return d;
}
void solve(){
ll a1,m1;
scanf("%d",&n);
scanf("%lld%lld",&m1,&a1);
bool ok = 1;
for(int i = 2; i <= n; i++){
ll a2,m2,k1,k2;
scanf("%lld%lld",&m2,&a2);
ll d = exgcd(m1,m2,k1,k2);
if((a2-a1)%d) ok = 0;
else{
k1 = mul(k1,(a2-a1)/d,m2/d);//这个地方必须要用取模快速乘
a1 = a1+k1*m1;
m1 = abs(m1/d*m2);
}
}
if(ok)printf("%lld\n",(a1%m1+m1)%m1);
}
int main()
{
solve();
return 0;
}