#本篇文章将比较长,请耐心观看#
单位元
要说明逆元,就要先说明单位元。单位元是集合中的一种特别的元,当它与其他元素结合时不会改变其他元素。a*e = a(其中的“*”是通配符,不单指乘法,可以代表+ - * /等其他运算符号.),那么我们说e就是单位元。
借助定义,我们就可以算出每个运算的单位元是什么。
加法的单位元
a + e = a ⇒ e = 0 a+e = a \Rightarrow e = 0 a+e=a⇒e=0,所以加法的单位元为0;
减法的单位元
a − e = a ⇒ e = 0 a-e=a\Rightarrow e=0 a−e=a⇒e=0,所以减法的单位元也为0;
乘法的单位元
a ∗ e = a ⇒ e = 1 a*e=a\Rightarrow e = 1 a∗e=a⇒e=1,所以乘法的单位元为1;
除法的单位元
a / e = a ⇒ e = 1 a/e=a\Rightarrow e=1 a/e=a⇒e=1,所以除法的单位元为1;
逆元
定义:在一个集合中,对于某种运算*;如果任意两个元素的运算结果等于单位元,则称这两个元素互为逆元。
加法的逆元
a + e = 0 ⇒ e = 0 a+e = 0 \Rightarrow e = 0 a+e=0⇒e=0,所以加法的逆元为-a;
减法的逆元
a − e = 0 ⇒ e = 0 a-e=0\Rightarrow e=0 a−e=0⇒e=0,所以减法的逆元为a;
乘法的逆元
a ∗ e = 1 ⇒ e = 1 a*e=1\Rightarrow e = 1 a∗e=1⇒e=1,所以乘法的逆元为 1 a \frac{1}{a} a1;
除法的逆元
a / e = 1 ⇒ e = 1 a/e=1\Rightarrow e=1 a/e=1⇒e=1,所以除法的逆元为a;
其实加法逆元就是相反数,乘法逆元就是倒数。
以上的四种运算法则很好理解,但是实际题目中并不多见,而最多见的当是模乘了。
(这里先说明取模和取余的区别)
假设a与b互质且a>b.
则有
a
=
k
b
+
r
a = kb + r
a=kb+r; 这里r为余数。
所以可以知道
r
=
a
−
a
b
b
r = a - \frac{a}{b}b
r=a−bab
当我们要取模时,
a
b
\frac{a}{b}
ba向负无穷大取整。
当我们要取余时,
a
b
\frac{a}{b}
ba向0取整。
所以取模的(正负)符号和a(被除数)有关,而余数恒大于0;
模乘的单位元与逆元
我们先来证明ab(mod n) = a(mod n) * b(mod n);
a
=
k
1
n
+
r
a
,
b
=
k
2
n
+
r
b
a = k_1n+r_a,b = k_2n+r_b
a=k1n+ra,b=k2n+rb
⇒
a
b
=
k
1
k
2
n
2
+
k
1
r
b
n
+
k
2
r
a
n
+
r
a
r
b
\Rightarrow ab=k_1k_2n^2+k_1r_bn+k_2r_an+r_ar_b
⇒ab=k1k2n2+k1rbn+k2ran+rarb
⇒
a
b
≡
r
a
r
b
(
m
o
d
n
)
\Rightarrow ab\equiv r_ar_b(mod\ n)
⇒ab≡rarb(mod n)
⇒
a
≡
r
a
(
m
o
d
n
)
,
b
≡
r
b
(
m
o
d
n
)
\Rightarrow a \equiv r_a(mod\ n),b\equiv r_b(mod\ n)
⇒a≡ra(mod n),b≡rb(mod n)
⇒
a
b
(
m
o
d
n
)
=
a
(
m
o
d
n
)
∗
b
(
m
o
d
n
)
\Rightarrow ab(mod\ n) = a(mod\ n)*b(mod\ n)
⇒ab(mod n)=a(mod n)∗b(mod n)
模乘的单位元
a e = a ( m o d n ) ⇒ e = 1 ae = a(mod\ n) \Rightarrow e = 1 ae=a(mod n)⇒e=1
模乘的逆元
a
e
=
1
(
m
o
d
n
)
⇒
e
=
a
−
1
ae = 1(mod\ n) \Rightarrow e=a^{-1}
ae=1(mod n)⇒e=a−1
那么问题来了,模乘的逆元怎么计算?
计算模乘逆元的方法
扩展欧几里得定理
也许你不知道欧几里得定理是什么,但我相信你肯定接触过,欧几里得定理就是我们求两数最大公约数的辗转相除法。
为了让扩展欧几里得定理更加形象,我们先看一个例题
给定正整数a和b,求满足等式ax+by=1的x的最小正整数解。如果不存在返回-1
我们先考虑x不存在的情况,我们通过欧几里得定理可以求出a和b的最大公约数为k
所以ax+by=1可以写成
k
(
a
k
x
+
b
k
y
)
=
1
k(\frac{a}{k}x+\frac{b}{k}y) = 1
k(kax+kby)=1两个正整数相乘结果为1只能是1*1,所以k = 1,而k为a和b的最大公约数,所以a和b互质。
(ps:可能有人不知道为什么
(
a
k
x
+
b
k
y
)
(\frac{a}{k}x+\frac{b}{k}y)
(kax+kby)和k是两个正整数啊,由于k为a和b的最大公约数,所以k一定是正整数,并且k能被a和b整除,而x,y也为整数,
所以
(
a
k
x
+
b
k
y
)
(\frac{a}{k}x+\frac{b}{k}y)
(kax+kby)为整数(当然不一定为正,但k一定是正数,想要结果为1,
(
a
k
x
+
b
k
y
)
(\frac{a}{k}x+\frac{b}{k}y)
(kax+kby)也必须要为正数))
那么当a和b不互质时,x不存在,直接返回-1;
现在考虑这个题目和上面介绍的逆元有什么关系?
上文说道模乘的逆元即:
a
e
≡
1
(
m
o
d
n
)
ae\equiv1(mod\ n)
ae≡1(mod n)
可以写成
a
e
=
1
+
k
n
⇒
a
e
−
k
n
=
1
ae = 1+kn \Rightarrow ae-kn=1
ae=1+kn⇒ae−kn=1
由于k是系数,令x=e,y=-k,b=n所以上式可以写成
a
x
+
b
y
=
1
ax+by=1
ax+by=1
这就找出了题目与模乘逆元的关系,我们要求x,就是求a的逆元。
又回到了求逆元=-=+
a
x
+
b
y
=
1
=
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
a
m
o
d
b
)
ax+by=1=gcd(a,b)=gcd(b,a\ mod\ b)
ax+by=1=gcd(a,b)=gcd(b,a mod b)
=
b
x
′
+
(
a
−
[
a
b
]
b
)
y
′
=bx'+(a-[\frac{a}{b}]b)y'
=bx′+(a−[ba]b)y′([]是向0取整)
=
a
y
′
+
b
(
x
′
−
[
a
b
]
y
′
)
=ay'+b(x'-[\frac{a}{b}]y')
=ay′+b(x′−[ba]y′)
所以可以得到
{
x
=
y
′
y
=
x
′
−
[
a
b
]
y
′
\begin{cases} x=y' \\ y=x'-[\frac{a}{b}]y' \end{cases}
{x=y′y=x′−[ba]y′
利用这个公式,就可以进行递推。
由欧几里得定理可知,a和b辗转相除到最后一步,a = gcd(a,b),b = 0
而这里gcd(a,b) = 1,所以递推到最后一步时,a = 1,b = 0,有一组整数解x = 1,y = 0使ax+by=1成立(因为b = 0,所以y可以取任何数,只需要最后算出的x再用x = (x%b+b)%b能得出结果,为什么要这一步,请看下文),然后以x=1,y=0为基础递归,通过上面的递归公式就可以得到答案。但是得到的x可能并不是最小正整数,还要让
x = (x%b+b)%b.(为什么要有这一步?)
x是a(mod b)的逆元,
a
x
≡
1
(
m
o
d
b
)
ax\equiv1(mod\ b)
ax≡1(mod b)
而
a
x
(
m
o
d
b
)
=
a
(
m
o
d
b
)
∗
x
(
m
o
d
b
)
ax(mod\ b) = a(mod\ b) * x(mod\ b)
ax(mod b)=a(mod b)∗x(mod b)
我们已经求出了一个x,但是这个x可能不是最小正整数解(可能是负数或者更大的数),所以我们让x%b使x接近b(可能还是负数,所以这里用接近这个词)再加上b保证它成为正数,但又可能不是最小,所以再%b。
举个栗子:
给定a = 10,b = 3.
10
x
+
3
y
=
1
10x+3y=1
10x+3y=1 (a = 10,b = 3)
⇒
3
x
+
y
=
1
\Rightarrow 3x+y = 1
⇒3x+y=1 (a = 3,b = 10%3=1)
⇒
x
=
1
\Rightarrow x = 1
⇒x=1 (a = 1,b = 3%1=0)
然后递归
x
=
1
,
y
=
0
x = 1,y = 0
x=1,y=0
⇒
x
=
0
,
y
=
1
−
[
3
1
]
∗
0
=
1
\Rightarrow x = 0,y = 1-[\frac{3}{1}]*0 = 1
⇒x=0,y=1−[13]∗0=1
⇒
x
=
1
,
y
=
0
−
[
10
3
]
∗
1
=
−
3
\Rightarrow x = 1,y = 0 -[\frac{10}{3}]*1=-3
⇒x=1,y=0−[310]∗1=−3
最后
x
=
(
1
%
3
+
3
)
%
3
=
1
x = (1 \%3 +3)\%3 = 1
x=(1%3+3)%3=1所以答案就是1;
代码写法:
void ko(int a,int b,int &x,int &y)
{
if(!b)
{
x = 1;
y = 0;//可以为任何数
return;
}
ko(b,a%b);
int tem = x;
x = y;
y = tem - a/b*y;
}
费马小定理
假如a式一个整数,p是一个质数,那么 a p − a a^p-a ap−a是p的倍数,也可以表示为 a p ≡ a ( m o d p ) a^p\equiv a(mod\ p) ap≡a(mod p).如果a与p互质的话,可以改写成 a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv1(mod\ p) ap−1≡1(mod p)
费马小定理的证明
(
n
+
1
)
p
=
n
p
+
C
p
1
n
p
−
1
+
.
.
.
+
C
p
p
−
1
n
+
1
(n+1)^p = n^p+C^{1}_{p}n^{p-1}+...+C^{p-1}_{p}n+1
(n+1)p=np+Cp1np−1+...+Cpp−1n+1
⇒
(
n
+
1
)
p
−
n
p
−
1
≡
0
(
m
o
d
p
)
\Rightarrow (n+1)^p-n^p-1\equiv0(mod\ p)
⇒(n+1)p−np−1≡0(mod p)
⇒
(
n
+
1
)
p
−
1
≡
n
p
(
m
o
d
p
)
\Rightarrow(n+1)^p-1\equiv n^p(mod\ p)
⇒(n+1)p−1≡np(mod p)
⇒
(
n
+
1
)
p
−
(
n
+
1
)
≡
n
p
−
n
(
m
o
d
p
)
\Rightarrow(n+1)^p-(n+1)\equiv n^p-n(mod\ p)
⇒(n+1)p−(n+1)≡np−n(mod p)
通过数学归纳法证明:
当p = 1时,
0
≡
0
(
m
o
d
1
)
0\equiv0(mod\ 1)
0≡0(mod 1)成立,
假设p=k时,
n
k
−
n
≡
0
(
m
o
d
p
)
n^k-n\equiv 0(mod\ p)
nk−n≡0(mod p)成立
当p = k+1时,
(
n
+
1
)
p
−
(
n
+
1
)
≡
n
p
−
n
(
m
o
d
p
)
≡
0
(
m
o
d
p
)
(n+1)^p-(n+1)\equiv n^p-n(mod\ p)\equiv 0(mod\ p)
(n+1)p−(n+1)≡np−n(mod p)≡0(mod p)
即证:
a
p
−
a
≡
0
(
m
o
d
p
)
a^p-a\equiv0(mod\ p)
ap−a≡0(mod p)
费马小定理求逆元
通过费马小定理我们知道:
a
p
−
1
≡
0
(
m
o
d
p
)
a^{p-1}\equiv0(mod\ p)
ap−1≡0(mod p)
⇒
a
∗
a
p
−
2
≡
0
(
m
o
d
p
)
\Rightarrow a*a^{p-2}\equiv0(mod\ p)
⇒a∗ap−2≡0(mod p)
所以,a的逆元就是
a
p
−
2
a^{p-2}
ap−2
上例题!
给定质数p和正整数a,求满足 a x ≡ 1 ( m o d p ) ax\equiv1(mod\ p) ax≡1(mod p)的最小正整数x,如果不存在则返回-1
和第一个例题十分相似,唯一不同的就是例题1中b可以不是质数,而该例题中p为质数。
同样,如果a和p不互质的话,返回-1;
如果a和p互质,那么我们只需要求出
a
p
−
2
a^{p-2}
ap−2即可
为了求出
a
p
−
2
a^{p-2}
ap−2,由于p-2可能很大,导致时间复杂度很高
我们需要引入新的算法“快速幂”来降低时间复杂度。
快速幂
先举个栗子·-·
a
(
22
)
10
=
a
(
10110
)
2
=
a
16
∗
a
4
∗
a
2
a^{{(22)}_{10}} = a^{{(10110)}_{2}} = a^{16}*a^{4}*a^2
a(22)10=a(10110)2=a16∗a4∗a2将原本的22次乘法缩短为3次乘法。
代码实现:
int quickmi(int a,int b)
{
int res = 1;
while(b)
{
//如果b当前该位为1,那么就要乘上a。
if(b & 1)res = res * a % MOD;//(一般都会要模一个数的)
//a是随着b的位移而平方。(即a的上标分别对应1 2 4 8 ...)
a = a * a % MOD;
b>>=1;//b向右位移
}
return res;
}
但是!,凡事都有意外,两数相乘时可能爆int,把
res = res * a % MOD;改成res = (long long)res * a %MOD可能也会爆long long
这个时候我们就需要一些小技巧了。
优化模数为64为整数技巧(我也不知道叫什么名字=-=、)
我们可以借鉴二进制快速幂的思想,把a * b中的b写成2进制的形式
举个栗子·-·
a
∗
(
22
)
10
%
M
O
D
=
a
∗
(
10110
)
2
%
M
O
D
=
(
a
∗
16
%
M
O
D
+
a
∗
4
%
M
O
D
+
a
∗
2
%
M
O
D
)
%
M
O
D
a*(22)_{10}\%MOD = a*(10110)_2\%MOD=(a*16\%MOD+a*4\%MOD+a*2\%MOD)\%MOD
a∗(22)10%MOD=a∗(10110)2%MOD=(a∗16%MOD+a∗4%MOD+a∗2%MOD)%MOD
这样就可以将一个大数拆分成很多个小数取模之后相加取模,这样可以避免爆数据结构的情况。
代码实现:
int goodmul(int a,int b)
{
int res = 0;//因为是加法,初始值为0;
while(b)
{
if(b & 1)res = (res + a)%MOD;
a = (a + a)%MOD
b>>=1;
}
return res;
}
然后快速幂的代码就可以改写为
int quickmi(int a,int b)
{
int res = 1;
while(b)
{
//如果b当前该位为1,那么就要乘上a。
if(b & 1)res = goodmul(a,res);//(一般都会要模一个数的)
//a是随着b的位移而平方。(即a的上标分别对应1 2 4 8 ...)
a = goolmul(a,a);
b>>=1;//b向右位移
}
return res;
}
ok,了解了快速幂与大数模乘优化之后,回到费马小定理求模乘逆元
直接调用quickmi(a,p-2)计算即可。
逆元有许多种运用,目前我所接触到的就是组合数与逆元
C
n
m
=
n
!
(
n
−
m
)
!
(
m
)
!
C^{m}_{n} = \frac{n!}{(n-m)!(m)!}
Cnm=(n−m)!(m)!n!,通常先求出阶乘,再相除,但是通常阶乘会很大导致爆int,这时我们就要用到逆元了。
组合数还可以写成
C
n
m
=
n
!
(
n
−
m
)
!
−
1
m
!
−
1
C^{m}_{n} = n!(n-m)!^{-1}m!^{-1}
Cnm=n!(n−m)!−1m!−1,其中
(
n
−
m
)
!
−
1
(n-m)!^{-1}
(n−m)!−1和
m
!
−
1
m!^{-1}
m!−1就分别是
(
n
−
m
)
!
(n-m)!
(n−m)!和
m
!
m!
m!的逆元,通过逆元把除法变成乘法,就可以使用一开始证明的公式直接模乘,求出组合数啦!