前言
上一次,我讲到了求解线性同余方程,并挖了一个线性求逆元的大坑,现在补上吧。
逆元
逆元的定义:若
a
∗
x
≡
1
(
m
o
d
b
)
a * x\equiv 1 \pmod b
a ∗ x ≡ 1 ( m o d b ) ,且
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 ( m o d p ) 然后,我们设
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 = ⌊ i p ⌋ ,而
r
=
p
%
i
r = p \% i
r = p % i 。 不难得到
p
≡
0
(
m
o
d
p
)
p \equiv 0 \pmod p
p ≡ 0 ( m o d p ) ,拆开得
k
∗
i
+
r
≡
0
(
m
o
d
p
)
k * i + r \equiv 0 \pmod p
k ∗ i + r ≡ 0 ( m o d p ) ,两边乘以
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 ( m o d p ) 得,
i
−
1
≡
−
k
∗
r
−
1
(
m
o
d
p
)
i^{-1} \equiv -k * r^{-1} \pmod p
i − 1 ≡ − k ∗ r − 1 ( m o d p ) 也就是
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 ≡ − ⌊ i p ⌋ ∗ ( p % i ) − 1 ( m o d p )
不难得出代码:
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 ( m o d 3 ) x ≡ 3 ( m o d 5 ) x ≡ 2 ( m o d 7 ) 通过上述方法的第一步,古人提示我们先考虑以下三个同余方程:
{
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 ( m o d 3 ) x ≡ 0 ( m o d 5 ) x ≡ 0 ( m o d 7 ) (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 ( m o d 3 ) x ≡ 1 ( m o d 5 ) x ≡ 0 ( m o d 7 ) (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 ( m o d 3 ) x ≡ 0 ( m o d 5 ) x ≡ 1 ( m o d 7 ) (3) 以第一个方程为研究对象,通过观察,不难发现第一组线性同余方程的解一定为5,7的公倍数,可写出一个公式为
35
∗
x
≡
1
(
m
o
d
3
)
35*x \equiv 1 \pmod 3
3 5 ∗ x ≡ 1 ( m o d 3 ) ,这个公式用拓展欧几里得可求出最小正整数解为70,那么,求出它又有什么用呢? 此时,我们不妨设
1
0
=
70
0
\begin{matrix} & 1 \\ & 0 & = & 70 \\ & 0 \\ \end{matrix}
1 0 0 = 7 0 即表示第一个同余方程组的解, 用
0
1
=
21
0
\begin{matrix} & 0 \\ & 1 & = & 21 \\ & 0 \\ \end{matrix}
0 1 0 = 2 1 表示第二个同余方程组的解,
0
0
=
15
1
\begin{matrix} & 0 \\ & 0 & = & 15 \\ & 1 \\ \end{matrix}
0 0 1 = 1 5 表示第三个同余方程组的解。 则
2
3
2
\begin{matrix} & 2 \\ & 3 \\ & 2 \\ \end{matrix}
2 3 2 表示原方程组的解 不难得出这些奇怪的数列相加得出的解满足条件,在取模的情况下,两个数相加时,它们之间的余数和若不大于取模用的数,则它们的和的余数为原先两个数的余数和,即
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}
2 3 2 = 2 ∗ 1 0 0 + 3 ∗ 0 1 0 + 2 ∗ 0 0 1 = 2 ∗ 7 0 + 3 ∗ 2 1 + 2 ∗ 1 5 ≡ 2 3 ( m o d 1 0 5 ) 将答案取模它们的最大公因数后,余数关系仍成立。 我们好像发现了什么,那就让我们从特殊推广到一般吧。 中国剩余定理的基本内容是(摘自《数学一本通》):设自然数
m
1
,
m
2
,
.
.
.
,
m
r
m_1,m_2,...,m_r
m 1 , m 2 , . . . , m r 两两互素 ,并记
N
=
m
1
∗
m
2
∗
.
.
.
∗
m
r
N = m_1*m_2*...*m_r
N = m 1 ∗ m 2 ∗ . . . ∗ m r ,则同余方程组:
{
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 ≡ b 1 ( m o d m 1 ) x ≡ b 2 ( m o d m 2 ) . . . . . . x ≡ b r ( m o d m r ) 在模
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 ( 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 ) 其中
1
≤
i
≤
r
1 \le i \le r
1 ≤ i ≤ r 。 由于这些数两两互素,令
x
i
=
(
N
m
i
)
x_i = ({N \over m_i} )
x i = ( m i N ) ,因为所有m互素,保证求出的答案不会增大其它
m
m
m 的余数,推出同余方程为
k
i
∗
x
i
≡
1
(
m
o
d
m
i
)
k_i*x_i \equiv 1 \pmod {m_i}
k i ∗ x i ≡ 1 ( m o d m i ) ,最终解为
b
i
∗
k
i
∗
x
i
b_i * k_i * x_i
b i ∗ k i ∗ x i 。将所有解加上并注意对
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 ≡ a 1 ( m o d b 1 ) x ≡ a 2 ( m o d b 2 ) . . . . . . x ≡ a k ( m o d b k ) 注意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 ≡ b 1 ( m o d m 1 ) x ≡ b 2 ( m o d m 2 ) . . . . . . x ≡ b r ( m o d m r ) 问题和上面差不多,不过自然数
m
1
,
m
2
,
.
.
.
,
m
r
m_1,m_2,...,m_r
m 1 , m 2 , . . . , m r 不再两两互素 。 这样上面的方法就不适用了,第一步的做法不能保证对一个数进行操作时,不会增大取模其它数的余数。 对于这一点我们考虑使用类似数学归纳法的做法,具体来说,共分为两点: 一,定义初始状态; 二,思考如何从当前状态转到下一个状态。 好吧,不玩深沉了,所谓定义初始状态,就是直接得出第一组的解,第一组的解就是
b
1
b_1
b 1 ; 所谓从当前状态转到下一个状态,就是合并思想,将当前这一组的解与前面所有组的解建立联系;不过为了方便,我们先考虑合并其中两个组,设为:
{
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 ≡ b i ( m o d m i ) x ≡ b j ( m o d m j ) 不难看出上式可表示为:
{
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 = m i ∗ k i + b i x = m j ∗ k j + b j 所以
m
i
∗
k
i
−
m
j
∗
k
j
=
b
j
−
b
i
m_i * k_i - m_j * k_j = b_j -b_i
m i ∗ k i − m j ∗ k j = b j − b i 设
a
=
m
i
a = m_i
a = m i ,
b
=
m
j
b = m_j
b = m j ,
c
=
b
j
−
b
i
c = b_j -b_i
c = b j − b i , 用扩欧求解同余方程
a
∗
x
′
≡
c
(
m
o
d
b
)
a * x' \equiv c \pmod {b}
a ∗ x ′ ≡ c ( m o d b ) , 不难得出
k
i
k_i
k i 的一种解,即为
x
′
∗
c
G
C
D
(
a
,
b
)
x' *{ c \over GCD(a,b)}
x ′ ∗ G C D ( a , b ) c 。 如果
c
%
G
C
D
(
a
,
b
)
!
=
0
c \% GCD(a,b) != 0
c % G C D ( a , b ) ! = 0 , 那么整个方程组无解。 设
z
=
m
j
G
C
D
(
m
i
,
m
j
)
z = {m_j \over GCD(m_i,m_j)}
z = G C D ( m i , m j ) m j ,将得出的
k
i
k_i
k i 经过计算
(
k
i
%
z
+
z
)
%
z
(k_i \%z+z)\%z
( k i % z + z ) % z 即可得到最小正整数解。 则两个公式的通解为
x
=
m
i
∗
k
i
+
b
i
x = m_i * k_i + b_i
x = m i ∗ k i + b i ,它可以对
L
C
M
(
m
i
,
m
j
)
LCM(m_i,m_j)
L C M ( m i , m j ) 取模,不影响余数的大小。 如果是合并当前组与所有在当前组的前面的组,
{
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 ( m o d M ) x ≡ b i ( m o d m i ) 其中
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})
L C M ( m 1 , m 2 , . . . , m i − 1 ) ,
N
N
N 已经保存了所有前
i
−
1
i - 1
i − 1 个的取余条件,所有满足
x
≡
N
(
m
o
d
M
)
x \equiv N \pmod {M}
x ≡ N ( m o d M ) 的数都能成为前
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 ∗ k 1 + N x = m i ∗ k 2 + b i 所以
M
∗
k
1
−
m
i
∗
k
2
=
b
i
−
N
M * k_1 - m_i * k_2 = b_i -N
M ∗ k 1 − m i ∗ k 2 = b i − 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;
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 ;
}