前言
上一次,我讲到了求解线性同余方程,并挖了一个线性求逆元的大坑,现在补上吧。
逆元
逆元的定义:若
a
∗
x
≡
1
(
m
o
d
b
)
a * x\equiv 1 \pmod b
a∗x≡1(modb),且
a
a
a,
b
b
b互质,(若不互质则不存在),则称
x
x
x为
a
a
a模
b
b
b意义下的逆元,记作
a
−
1
a^{-1}
a−1。
逆元的求法:
单个:拓展欧几里得,上一篇已经讲过了。
批量:线性逆元。
假设求模
p
p
p的逆元。
具体如下:首先,
1
−
1
≡
1
(
m
o
d
p
)
1^{-1} \equiv 1 \pmod p
1−1≡1(modp)
然后,我们设
p
=
k
∗
i
+
r
p = k * i + r
p=k∗i+r,其中
k
,
i
,
r
k,i,r
k,i,r均为整数,且
r
<
k
r < k
r<k,其实
k
=
⌊
p
i
⌋
k = \lfloor { p \over i}\rfloor
k=⌊ip⌋,而
r
=
p
%
i
r = p \% i
r=p%i。
不难得到
p
≡
0
(
m
o
d
p
)
p \equiv 0 \pmod p
p≡0(modp),拆开得
k
∗
i
+
r
≡
0
(
m
o
d
p
)
k * i + r \equiv 0 \pmod p
k∗i+r≡0(modp),两边乘以
i
−
1
∗
r
−
1
i^{-1}*r^{-1}
i−1∗r−1得(在模
p
p
p意义下相等是成立的):
k
∗
r
−
1
+
i
−
1
≡
0
(
m
o
d
p
)
k * r^{-1} + i^{-1} \equiv 0 \pmod p
k∗r−1+i−1≡0(modp)得,
i
−
1
≡
−
k
∗
r
−
1
(
m
o
d
p
)
i^{-1} \equiv -k * r^{-1} \pmod p
i−1≡−k∗r−1(modp)也就是
i
−
1
≡
−
⌊
p
i
⌋
∗
(
p
%
i
)
−
1
(
m
o
d
p
)
i^{-1} \equiv - \lfloor { p \over i}\rfloor * ( p \% i)^{-1} \pmod p
i−1≡−⌊ip⌋∗(p%i)−1(modp)
不难得出代码:
inv[i] = -(p/i) * inv[p%i];
非常简洁, O ( n ) O(n) O(n)的时间可得出答案。
求逆元还有用费马小定理的方法,但本蒟蒻不会 ,就不赘述了。
中国剩余定理
中国剩余定理是用来求线性同余方程组的解的,出自《孙子算经》中的题目:今有物不知其数,三三数之剩二(模3得2),五五数之剩三(模5得3),七七数之剩二(模7得2),问物几何?这个问题就是孙子问题,也就是中国剩余定理的由来了。
古人的口诀为:三人同行七十稀,五树梅花廿一支,七子团圆正半月,除百零五便得知。
翻译过来为:
第一步,找出5和7的公倍数中模3得1的最小数为70,找出3和7的公倍数中模5得3的最小数为21,找出3和5的公倍数中模7得1的最小数为15;
第二步,用70乘以2,21乘以3,15乘以2,得出和为233,不难发现,这个数模3得2,模5得3,模7得2;
第三步,算出3,5,7三个数的最小公倍数为105,将233除以105,得到余数为23,这个即为符合要求的最小正整数。
原问题用严谨的数学语言表述就是:
{
x
≡
2
(
m
o
d
3
)
x
≡
3
(
m
o
d
5
)
x
≡
2
(
m
o
d
7
)
\begin{cases} x \equiv 2 \pmod {3} \\[1ex] x \equiv 3 \pmod {5} \\[1ex] x \equiv 2 \pmod {7} \end{cases}
⎩⎪⎪⎨⎪⎪⎧x≡2(mod3)x≡3(mod5)x≡2(mod7)
通过上述方法的第一步,古人提示我们先考虑以下三个同余方程:
{
x
≡
1
(
m
o
d
3
)
x
≡
0
(
m
o
d
5
)
(1)
x
≡
0
(
m
o
d
7
)
\begin{cases} x \equiv 1 \pmod {3} \\[1ex] x \equiv 0 \pmod {5} & & & \text{(1)} \\[1ex] x \equiv 0 \pmod {7} \end{cases}
⎩⎪⎪⎨⎪⎪⎧x≡1(mod3)x≡0(mod5)x≡0(mod7)(1)
{
x
≡
0
(
m
o
d
3
)
x
≡
1
(
m
o
d
5
)
(2)
x
≡
0
(
m
o
d
7
)
\begin{cases} x \equiv 0 \pmod {3} \\[1ex] x \equiv 1 \pmod {5}&&& \text{(2)} \\[1ex] x \equiv 0 \pmod {7} \end{cases}
⎩⎪⎪⎨⎪⎪⎧x≡0(mod3)x≡1(mod5)x≡0(mod7)(2)
{
x
≡
0
(
m
o
d
3
)
x
≡
0
(
m
o
d
5
)
(3)
x
≡
1
(
m
o
d
7
)
\begin{cases} x \equiv 0 \pmod {3} \\[1ex] x \equiv 0 \pmod {5}&&& \text{(3)} \\[1ex] x \equiv 1 \pmod {7} \end{cases}
⎩⎪⎪⎨⎪⎪⎧x≡0(mod3)x≡0(mod5)x≡1(mod7)(3)
以第一个方程为研究对象,通过观察,不难发现第一组线性同余方程的解一定为5,7的公倍数,可写出一个公式为
35
∗
x
≡
1
(
m
o
d
3
)
35*x \equiv 1 \pmod 3
35∗x≡1(mod3),这个公式用拓展欧几里得可求出最小正整数解为70,那么,求出它又有什么用呢?
此时,我们不妨设
1
0
=
70
0
\begin{matrix} & 1 \\ & 0 & = & 70 \\ & 0 \\ \end{matrix}
100=70
即表示第一个同余方程组的解,
用
0
1
=
21
0
\begin{matrix} & 0 \\ & 1 & = & 21 \\ & 0 \\ \end{matrix}
010=21
表示第二个同余方程组的解,
0
0
=
15
1
\begin{matrix} & 0 \\ & 0 & = & 15 \\ & 1 \\ \end{matrix}
001=15
表示第三个同余方程组的解。
则
2
3
2
\begin{matrix} & 2 \\ & 3 \\ & 2 \\ \end{matrix}
232表示原方程组的解
不难得出这些奇怪的数列相加得出的解满足条件,在取模的情况下,两个数相加时,它们之间的余数和若不大于取模用的数,则它们的和的余数为原先两个数的余数和,即
a
%
p
+
b
%
p
=
(
a
+
b
)
%
p
a \% p + b \% p =(a + b) \% p
a%p+b%p=(a+b)%p。
最后得出以下式子:
2
1
0
0
3
=
2
∗
0
+
3
∗
1
+
2
∗
0
=
2
∗
70
+
3
∗
21
+
2
∗
15
≡
23
(
m
o
d
105
)
2
0
0
1
\begin{matrix} & 2 & & & & 1& &&&0&&&&0\\ & 3 & = & 2 & * & 0 & + & 3 & * & 1&+&2&*&0 & = 2 * 70 + 3 * 21 + 2 * 15 \equiv23 \pmod {105} \\ & 2& & & & 0 &&&&0&&&&1\\ \end{matrix}
232=2∗100+3∗010+2∗001=2∗70+3∗21+2∗15≡23(mod105)
将答案取模它们的最大公因数后,余数关系仍成立。
我们好像发现了什么,那就让我们从特殊推广到一般吧。
中国剩余定理的基本内容是(摘自《数学一本通》):设自然数
m
1
,
m
2
,
.
.
.
,
m
r
m_1,m_2,...,m_r
m1,m2,...,mr两两互素,并记
N
=
m
1
∗
m
2
∗
.
.
.
∗
m
r
N = m_1*m_2*...*m_r
N=m1∗m2∗...∗mr,则同余方程组:
{
x
≡
b
1
(
m
o
d
m
1
)
x
≡
b
2
(
m
o
d
m
2
)
.
.
.
.
.
.
x
≡
b
r
(
m
o
d
m
r
)
\begin{cases} x \equiv b_1\pmod {m_1} \\[1ex] x \equiv b_2\pmod {m_2} \\[1ex] ...\\[1ex] ...\\[1ex] x \equiv b_r\pmod {m_r} \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧x≡b1(modm1)x≡b2(modm2)......x≡br(modmr)在模
N
N
N同余的意义下有唯一解。
根据上述方法,考虑同余方程组
{
x
≡
0
(
m
o
d
m
1
)
.
.
.
x
≡
0
(
m
o
d
m
i
−
1
)
x
≡
1
(
m
o
d
m
i
)
x
≡
0
(
m
o
d
m
i
+
1
)
.
.
.
x
≡
0
(
m
o
d
m
r
)
\begin{cases} x \equiv 0 \pmod {m_1} \\[1ex] ...\\[1ex] x \equiv 0 \pmod {m_{i - 1}} \\[1ex] x \equiv 1 \pmod {m_i} \\[1ex] x \equiv 0 \pmod {m_{i + 1}} \\[1ex] ...\\[1ex] x \equiv 0 \pmod {m_r} \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧x≡0(modm1)...x≡0(modmi−1)x≡1(modmi)x≡0(modmi+1)...x≡0(modmr)
其中
1
≤
i
≤
r
1 \le i \le r
1≤i≤r。
由于这些数两两互素,令
x
i
=
(
N
m
i
)
x_i = ({N \over m_i} )
xi=(miN),因为所有m互素,保证求出的答案不会增大其它
m
m
m的余数,推出同余方程为
k
i
∗
x
i
≡
1
(
m
o
d
m
i
)
k_i*x_i \equiv 1 \pmod {m_i}
ki∗xi≡1(modmi),最终解为
b
i
∗
k
i
∗
x
i
b_i * k_i * x_i
bi∗ki∗xi。将所有解加上并注意对
N
N
N取模即可。
板子
[TJOI2009]猜数字 https://www.luogu.org/problem/P3868
本题等同于求解
{
x
≡
a
1
(
m
o
d
b
1
)
x
≡
a
2
(
m
o
d
b
2
)
.
.
.
.
.
.
x
≡
a
k
(
m
o
d
b
k
)
\begin{cases} x \equiv a_1\pmod {b_1} \\[1ex] x \equiv a_2\pmod {b_2} \\[1ex] ...\\[1ex] ...\\[1ex] x \equiv a_k\pmod {b_k} \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧x≡a1(modb1)x≡a2(modb2)......x≡ak(modbk)
注意a可能为负,以及数据过大要用"快(gui)速乘"。
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
#define N 13
using namespace std;
ll a[N] , b[N] , k , n;
inline void read( ll & res ) {
res = 0;
ll pd = 1;
char aa = getchar();
while ( aa < '0' || aa > '9' ) {
if ( aa == '-' ) {
pd = -pd;
}
aa = getchar();
}
while ( aa >= '0' && aa <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
aa = getchar();
}
res *= pd;
return;
}
inline ll mul( ll tem , ll t ) {
ll ans = 0;
while ( t ) {
if ( t & 1 ) {
ans = ( ans + tem ) % n;
}
tem = ( tem + tem ) % n;
t >>= 1;
}
return ans;
}
inline ll exgcd( ll a , ll b , ll & x , ll & y ) {
if ( !b ) {
x = 1;
y = 0;
return a;
}
ll d = exgcd( b , a % b , x , y );
ll tem = x;
x = y;
y = tem - a / b * y;
return d;
}
inline ll china(){
ll res = 0;
for ( int i = 1 ; i <= k ; ++i ) {
ll x , y;
exgcd( n / b[i] , b[i] , x , y );
res = ( res + mul( mul( x , n/b[i] ) % n , a[i] ) % n ) % n;
}
return( res % n + n ) % n;
}
int main () {
n = 1;
read(k);
for ( int i = 1 ; i <= k ; ++i ) {
read(a[i]);
}
for ( int i = 1 ; i <= k ; ++i ) {
read(b[i]);
n *= b[i];
a[i] = ( a[i] % b[i] + b[i] ) % b[i];
}
printf("%lld",china());
return 0;
}
扩展中国剩余定理
求解:
{
x
≡
b
1
(
m
o
d
m
1
)
x
≡
b
2
(
m
o
d
m
2
)
.
.
.
.
.
.
x
≡
b
r
(
m
o
d
m
r
)
\begin{cases} x \equiv b_1\pmod {m_1} \\[1ex] x \equiv b_2\pmod {m_2} \\[1ex] ...\\[1ex] ...\\[1ex] x \equiv b_r\pmod {m_r} \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧x≡b1(modm1)x≡b2(modm2)......x≡br(modmr)
问题和上面差不多,不过自然数
m
1
,
m
2
,
.
.
.
,
m
r
m_1,m_2,...,m_r
m1,m2,...,mr不再两两互素。
这样上面的方法就不适用了,第一步的做法不能保证对一个数进行操作时,不会增大取模其它数的余数。
对于这一点我们考虑使用类似数学归纳法的做法,具体来说,共分为两点:
一,定义初始状态;
二,思考如何从当前状态转到下一个状态。
好吧,不玩深沉了,所谓定义初始状态,就是直接得出第一组的解,第一组的解就是
b
1
b_1
b1;
所谓从当前状态转到下一个状态,就是合并思想,将当前这一组的解与前面所有组的解建立联系;不过为了方便,我们先考虑合并其中两个组,设为:
{
x
≡
b
i
(
m
o
d
m
i
)
x
≡
b
j
(
m
o
d
m
j
)
\begin{cases} x \equiv b_i\pmod {m_i} \\[1ex] x \equiv b_j\pmod {m_j} \\[1ex] \end{cases}
⎩⎨⎧x≡bi(modmi)x≡bj(modmj)不难看出上式可表示为:
{
x
=
m
i
∗
k
i
+
b
i
x
=
m
j
∗
k
j
+
b
j
\begin{cases} x = m_i * k_i + b_i \\[1ex] x = m_j * k_j + b_j \\[1ex] \end{cases}
⎩⎨⎧x=mi∗ki+bix=mj∗kj+bj
所以
m
i
∗
k
i
−
m
j
∗
k
j
=
b
j
−
b
i
m_i * k_i - m_j * k_j = b_j -b_i
mi∗ki−mj∗kj=bj−bi
设
a
=
m
i
a = m_i
a=mi,
b
=
m
j
b = m_j
b=mj,
c
=
b
j
−
b
i
c = b_j -b_i
c=bj−bi,
用扩欧求解同余方程
a
∗
x
′
≡
c
(
m
o
d
b
)
a * x' \equiv c \pmod {b}
a∗x′≡c(modb),
不难得出
k
i
k_i
ki的一种解,即为
x
′
∗
c
G
C
D
(
a
,
b
)
x' *{ c \over GCD(a,b)}
x′∗GCD(a,b)c。
如果
c
%
G
C
D
(
a
,
b
)
!
=
0
c \% GCD(a,b) != 0
c%GCD(a,b)!=0,
那么整个方程组无解。
设
z
=
m
j
G
C
D
(
m
i
,
m
j
)
z = {m_j \over GCD(m_i,m_j)}
z=GCD(mi,mj)mj,将得出的
k
i
k_i
ki经过计算
(
k
i
%
z
+
z
)
%
z
(k_i \%z+z)\%z
(ki%z+z)%z即可得到最小正整数解。
则两个公式的通解为
x
=
m
i
∗
k
i
+
b
i
x = m_i * k_i + b_i
x=mi∗ki+bi,它可以对
L
C
M
(
m
i
,
m
j
)
LCM(m_i,m_j)
LCM(mi,mj)取模,不影响余数的大小。
如果是合并当前组与所有在当前组的前面的组,
{
x
≡
N
(
m
o
d
M
)
x
≡
b
i
(
m
o
d
m
i
)
\begin{cases} x \equiv N \pmod {M} \\[1ex] x \equiv b_i\pmod {m_i} \\[1ex] \end{cases}
⎩⎨⎧x≡N(modM)x≡bi(modmi)其中
N
N
N表示前
i
−
1
i - 1
i−1组的解,M表示为
L
C
M
(
m
1
,
m
2
,
.
.
.
,
m
i
−
1
)
LCM(m_1 , m_2 , ... , m_{i - 1})
LCM(m1,m2,...,mi−1),
N
N
N已经保存了所有前
i
−
1
i - 1
i−1个的取余条件,所有满足
x
≡
N
(
m
o
d
M
)
x \equiv N \pmod {M}
x≡N(modM)的数都能成为前
i
−
1
i - 1
i−1个方程的解。此时,只要再满足第
i
i
i个方程,就可以得出前
i
i
i个方程的通解。
得出:
{
x
=
M
∗
k
1
+
N
x
=
m
i
∗
k
2
+
b
i
\begin{cases} x = M * k_1 + N \\[1ex] x = m_i * k_2 + b_i \\[1ex] \end{cases}
⎩⎨⎧x=M∗k1+Nx=mi∗k2+bi
所以
M
∗
k
1
−
m
i
∗
k
2
=
b
i
−
N
M * k_1 - m_i * k_2 = b_i -N
M∗k1−mi∗k2=bi−N。
按照上面合并两个同余方程的方法,用扩欧不难得出答案。
按照这种方法,从1一直递推到
r
r
r,就可以得出结果。
板子
【模板】扩展中国剩余定理(EXCRT)https://www.luogu.org/problem/P4777
就是一道模板题,不过注意要用“快(gui)速乘”。
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
const int N = 1e5 + 5;
ll aa[N] , bb[N];
ll n;
inline void read( ll & res ) {
res = 0;
ll pd = 1;
char a = getchar();
while ( a < '0' || a > '9' ) {
if ( a == '-' ) {
pd = -pd;
}
a = getchar();
}
while ( a >= '0' && a <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
a = getchar();
}
res *= pd;
return;
}
inline ll mul( ll a , ll b , ll mod ) {
ll ans = 0;
while ( b ) {
if ( b & 1 ) {
ans = ( ans + a ) % mod;
}
a = ( a + a ) % mod;
b >>= 1;
}
return ans;
}
inline ll exgcd( ll a , ll b , ll & x , ll & y ) {
if ( !b ) {
x = 1;
y = 0;
return a;
}
ll d = exgcd( b , a % b , x , y );
ll t = x;
x = y;
y = t - a / b * y;
return d;
}
inline ll china() {
ll ans = bb[1];
ll m = aa[1];
for ( int i = 2 ; i <= n ; ++i ) {
ll a = m;
ll b = aa[i];
ll c = ( bb[i] - ans % b + b ) % b;//式子在模b后仍成立,这里是为了保证c是正整数
ll x , y;
ll d = exgcd( a , b , x , y );
if ( c % d != 0 ) {
return -1;
}
ll tem = b/d;
ll res = mul( x , c/d , tem );
ans += res * m;
m *= tem;
ans = ( ans % m + m ) % m;
}
return ( ans % m + m ) % m;
}
int main () {
read(n);
for ( int i = 1 ; i <= n ; ++i ) {
read(aa[i]);
read(bb[i]);
}
printf("%lld",china());
return 0;
}