废话
这玩意跟中国剩余定理没有半毛钱关系,所以所以没学过中国剩余定理的完全不用担心。
当然还是建议先学一学中国剩余定理。
介绍
这是一个用来求形如下面这样的同余方程组的解的算法:
{
x
≡
a
1
(
m
o
d
m
1
)
x
≡
a
2
(
m
o
d
m
2
)
⋮
x
≡
a
n
(
m
o
d
m
n
)
\begin{cases} x \equiv a_1 \pmod {m_1}\\ x \equiv a_2 \pmod {m_2}\\ ~~\vdots\\ x \equiv a_n \pmod {m_n}\\ \end{cases}
⎩
⎨
⎧x≡a1(modm1)x≡a2(modm2) ⋮x≡an(modmn)
其中, m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m1,m2,...,mn 之间不一定互质。
做法
很神奇,网上有两个版本,有两种不同的做法。(并不知道哪一个是正版……)
做法 1 1 1
假设我们已经求出了前 p p p 个方程组的解 x 1 x_1 x1。
不妨设 M = ∏ i = 1 p m i M=\prod\limits_{i=1}^p {m_i} M=i=1∏pmi,那么前 p p p 个方程组的通解为 x 1 + k M ( k ∈ Z ) x_1+kM~~(k\in Z) x1+kM (k∈Z)。
那么要满足第
p
+
1
p+1
p+1 个方程,相当于要找到一个合法的
k
k
k,满足:
x
1
+
k
M
≡
a
p
+
1
(
m
o
d
m
p
+
1
)
x_1+kM \equiv a_{p+1} \pmod {m_{p+1}}
x1+kM≡ap+1(modmp+1)
相当于
x
1
+
k
M
+
L
m
p
+
1
=
a
p
+
1
(
L
∈
Z
)
(
m
o
d
m
p
+
1
)
x_1+kM+Lm_{p+1} = a_{p+1}~~(L\in Z) \pmod {m_{p+1}}
x1+kM+Lmp+1=ap+1 (L∈Z)(modmp+1)
移项得
k
M
+
L
m
p
+
1
=
a
p
+
1
−
x
1
(
m
o
d
m
p
+
1
)
kM+Lm_{p+1} = a_{p+1}-x_1 \pmod {m_{p+1}}
kM+Lmp+1=ap+1−x1(modmp+1)
用扩展欧几里得算法求出 k , L k,L k,L 即可,那么对于前 p + 1 p+1 p+1 个方程的解就是 x 1 + k M x_1+kM x1+kM。
但是这种做法有一个限制,就是 M M M 要在 l o n g l o n g long~long long long 范围内,而有些恶心题就不保证。
有一个小优化就是稍稍变一下 M M M 的定义,让 M = l c m i = 1 p m i M=lcm_{i=1}^p {m_i} M=lcmi=1pmi,这样可以稍稍减小一点 M M M。
代码如下:
#include <cstdio>
#include <cstring>
#define ll long long
#define maxn 100010
int n;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)return x=1,y=0,a;
ll d=exgcd(b,a%b,y,x);
return y-=a/b*x,d;
}
ll a[maxn],m[maxn];
ll ksc(ll x,ll y,ll mod)
{
ll re=0,tot=x;
while(y)
{
if(y&1ll)re=(re+tot)%mod;
tot=(tot+tot)%mod;
y>>=1;
}
return re;
}
ll excrt()
{
ll M=m[1],x=a[1];
for(int i=2;i<=n;i++)
{
ll k,L,c=(a[i]-x%m[i]+m[i])%m[i];
ll gcd=exgcd(M,m[i],k,L),bg=m[i]/gcd;
k=ksc(k,c/gcd,bg);
x+=k*M;
M*=bg;
x=(x%M+M)%M;
}
return x;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld %lld",&m[i],&a[i]);
printf("%lld\n",excrt());
}
做法 2 2 2
这种做法就是大力将两个同余式和并为一个,然后大力合并所有同余式。
假设现在有两个同余式:
{
x
≡
a
1
(
m
o
d
m
1
)
x
≡
a
2
(
m
o
d
m
2
)
\begin{cases} x\equiv a_1 \pmod {m_1}\\ x\equiv a_2 \pmod {m_2} \end{cases}
{x≡a1(modm1)x≡a2(modm2)
那么有
{
x
=
a
1
+
k
1
m
1
x
=
a
2
+
k
2
m
2
\begin{cases} x=a_1+k_1m_1\\ x=a_2+k_2m_2\\ \end{cases}
{x=a1+k1m1x=a2+k2m2
则有
a
1
+
k
1
m
1
=
a
2
+
k
2
m
2
k
1
m
1
=
a
2
−
a
1
+
k
2
m
2
\begin{aligned} a_1+k_1m_1&=a_2+k_2m_2\\ k_1m_1&=a_2-a_1+k_2m_2 \end{aligned}
a1+k1m1k1m1=a2+k2m2=a2−a1+k2m2
设
g
=
gcd
(
m
1
,
m
2
)
g=\gcd(m_1,m_2)
g=gcd(m1,m2),将柿子除以
g
g
g,则有:
k
1
m
1
g
=
a
2
−
a
1
g
+
k
2
m
2
g
k
1
m
1
g
≡
a
2
−
a
1
g
(
m
o
d
m
2
g
)
k
1
=
a
2
−
a
1
g
×
i
n
v
(
m
1
g
)
+
y
×
m
2
g
\begin{aligned} \frac {k_1m_1} g&=\frac {a_2-a_1} g +\frac {k_2m_2} g\\ \frac {k_1m_1} g&\equiv\frac {a_2-a_1} g \pmod {\frac {m_2} g}\\ k_1&= \frac {a_2-a_1} g \times inv(\frac {m_1} g)+y \times \frac {m_2} g \end{aligned}
gk1m1gk1m1k1=ga2−a1+gk2m2≡ga2−a1(modgm2)=ga2−a1×inv(gm1)+y×gm2
其中, i n v inv inv 是指逆元。
将
k
1
k_1
k1 带回去,有
x
=
a
1
+
m
1
×
(
a
2
−
a
1
g
×
i
n
v
(
m
1
g
)
+
y
×
m
2
g
)
x
=
a
1
+
m
1
×
a
2
−
a
1
g
×
i
n
v
(
m
1
g
)
+
y
×
m
1
m
2
g
x
≡
a
1
+
m
1
×
a
2
−
a
1
g
×
i
n
v
(
m
1
g
)
(
m
o
d
m
1
m
2
g
)
\begin{aligned} x&=a_1+m_1 \times \left(\frac {a_2-a_1} g \times inv(\frac {m_1} g)+y \times \frac {m_2} g \right)\\ x&=a_1+m_1 \times \frac {a_2-a_1} g \times inv(\frac {m_1} g) + y \times \frac {m_1m_2} g\\ x&\equiv a_1+m_1 \times \frac {a_2-a_1} g \times inv(\frac {m_1} g) \pmod {\frac {m_1m_2} g} \end{aligned}
xxx=a1+m1×(ga2−a1×inv(gm1)+y×gm2)=a1+m1×ga2−a1×inv(gm1)+y×gm1m2≡a1+m1×ga2−a1×inv(gm1)(modgm1m2)
于是两条同余式合并成了一条。
但是要注意,里面 a 2 − a 1 g × i n v ( m 1 g ) \frac {a_2-a_1} g \times inv(\frac {m_1} g) ga2−a1×inv(gm1) 这一部分依然要在模 m 2 g \frac {m_2} g gm2 意义下计算,但是前面的 × m 1 \times m_1 ×m1 在模 m 1 m 2 g \frac {m_1m_2} g gm1m2 意义下计算。
当 g g g 不能整除 a 2 − a 1 a_2-a_1 a2−a1 时,该方程组无解。
代码如下:
#include <cstdio>
#include <cstring>
#define ll long long
#define maxn 100010
int n;
ll gcd(ll x,ll y){return y==0?x:gcd(y,x%y);}
void exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0){x=1,y=0;return;}
exgcd(b,a%b,y,x);
y-=a/b*x;
}
ll inv(ll a,ll mod)
{
ll x,y;
exgcd(a,mod,x,y);
return (x%mod+mod)%mod;
}
ll ksc(ll x,ll y,ll mod)
{
ll re=0,tot=x;
y=(y%mod+mod)%mod;
while(y)
{
if(y&1ll)re=(re+tot)%mod;
tot=(tot+tot)%mod;
y>>=1;
}
return re;
}
ll a[maxn],m[maxn];
ll excrt()
{
ll a1=a[1],m1=m[1],a2,m2,g;
for(int i=2;i<=n;i++)
{
a2=a[i],m2=m[i];
g=gcd(m1,m2);
if((a2-a1)%g!=0)return -1;
a1+=ksc(inv(m1/g,m2/g),(a2-a1)/g,m2/g)%(m2/g)*m1;
m1*=m2/g;
a1=(a1%m1+m1)%m1;
}
return a1;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld %lld",&m[i],&a[i]);
printf("%lld",excrt());
}