板块:数论
前置知识:同余式、费马小定理、裴蜀定理
难度:中等
前置知识一览:
冷知识: ⊥ \perp ⊥ 符号在数论中表示互质。
逆元,全称逆元素,是指一个可以取消另一给定元素运算的元素。例如,对于
a
x
×
1
a
ax\times \frac{1}{a}
ax×a1,由于最后
x
x
x 的值 没有发生变化,所以我们称
a
a
a 和
1
a
\frac{1}{a}
a1 互为逆元。
a
b
≡
a
x
(
m
o
d
p
)
,
b
⊥
p
\frac{a}{b}\equiv ax (\bmod \ p),b\perp p
ba≡ax(mod p),b⊥p
对于图示的同余式,我们称
x
x
x 是
b
b
b 在模
p
p
p 意义下的乘法逆元,记作
b
−
1
b^{-1}
b−1。0没有乘法逆元。
解决乘法逆元的方法有很多,常见的是费马小定理求乘法逆元、扩展欧几里得算法求乘法逆元、线性递推、离线求乘法逆元。
对乘法逆元定义式作如下推导:
由定义式两边同时乘
b
b
b 得,
a
≡
a
b
x
(
m
o
d
p
)
a\equiv abx(\bmod \ p)
a≡abx(mod p)
两边同时除以
a
a
a 得,
b
x
≡
1
(
m
o
d
p
)
bx\equiv1(mod \ p)
bx≡1(mod p)
因此,
a
b
≡
a
x
(
m
o
d
p
)
⟺
b
x
≡
1
(
m
o
d
p
)
\frac{a}{b}\equiv ax (\bmod \ p)\iff bx\equiv 1(\bmod \ p)
ba≡ax(mod p)⟺bx≡1(mod p)
时间复杂度
O
(
l
o
g
p
)
O(log p)
O(logp)。
已知费马小定理为
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1}\equiv 1(\bmod \ p)
ap−1≡1(mod p),将
a
p
−
1
a^{p-1}
ap−1 拆解为
a
×
a
p
−
2
a\times a^{p-2}
a×ap−2,再结合乘法逆元推导式
a
x
≡
1
(
m
o
d
p
)
ax\equiv 1(\bmod \ p)
ax≡1(mod p) 观察可得逆元
x
=
a
p
−
2
x=a^{p-2}
x=ap−2 ,也就是说,求一个数的乘法逆元,相当于求这个数的
p
−
2
p-2
p−2 次方。当然,由于费马小定理的硬性要求,必须满足模数是质数才可以使用该方法。
为了使计算次幂的速度更快,我们可以使用快速幂求解该问题。
参考代码如下:
#include
#include
#define int long long
using namespace std;
int n, p;
int quickm (int a, int b, int p)
{
int res = 1;
while (b)
{
if (b & 1) res = res * a % p;
b >>= 1;
a = a * a % p;
}
return res;
}
signed main()
{
cin >> n >> p;
for (int i = 1; i <= n; i++)
{
cout << quickm(i, p - 2, p) << endl;
}
return 0;
}
(主要摘抄于参考资料[1],毕竟也是学习笔记嘛,如果原作者介意可以私信笔者删除)
时间复杂度
O
(
l
o
g
p
)
O(log p)
O(logp)
我们可以受 exgcd 的启发,将乘法逆元式
a
x
≡
1
(
m
o
d
p
)
ax\equiv 1(\bmod \ p)
ax≡1(mod p) 转化为
a
x
+
b
y
=
1
ax+by=1
ax+by=1。
对乘法逆元式作如下推导:
将其转化为:
a
x
m
o
d
p
=
1
m
o
d
p
ax \bmod p = 1 \bmod p
axmodp=1modp
移项,得
(
a
x
−
1
)
m
o
d
p
=
0
(ax-1)\bmod p=0
(ax−1)modp=0
由此得,
p
∣
(
a
x
−
1
)
p|(ax-1)
p∣(ax−1)
设
a
x
−
1
ax-1
ax−1 为
p
p
p 的
k
k
k 倍,即
a
x
−
1
=
k
p
ax-1=kp
ax−1=kp,继续移项,得
a
x
−
k
p
=
1
ax-kp=1
ax−kp=1
令
y
=
−
k
y=-k
y=−k,即可转化为
a
x
+
p
y
=
1
①
ax+py=1①
ax+py=1①,根据裴蜀定理,我们知道所得方程
①
①
①有解的条件是
gcd
(
a
,
p
)
∣
1
\gcd(a,p)|1
gcd(a,p)∣1,即
gcd
(
a
,
p
)
=
1
\gcd(a,p)=1
gcd(a,p)=1,
a
⊥
p
a\perp p
a⊥p。因此我们不难得出一个结论:
a
a
a 在模
p
p
p 意义下的乘法逆元存在当且仅当
a
,
p
a,p
a,p 互质。
因为 gcd ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1,所以可以直接套用 exgcd 求解,即求解 a x + p y = gcd ( a , p ) ax+py=\gcd(a,p) ax+py=gcd(a,p), x x x 就是我们要求的乘法逆元。
参考代码:
#include
#include
#include
using namespace std;
typedef long long LL;
LL a, p, x = 0, y = 0;
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;
}
int main()
{
scanf("%lld%lld", &a, &p);
exgcd(a, p, x, y);
printf("%lld", (x % p + p) % p); //防负数小妙招
return 0;
}
如果采用上面方法求解逆元,我们可以再 O ( n l o g p ) O(n log p) O(nlogp) 的时间复杂度内,求出 [ 1 , n ] ( 1 ≤ n ≤ p ) [1,n](1\leq n\leq p) [1,n](1≤n≤p)的所有在模质数 p p p 意义下的乘法逆元,但如果针对下面这题的数据范围时,我们或许需要考虑更快速的方法,于是我们引入了线性求乘法逆元的方法。
基于逆元的定义,显然 1 在任意正整数模数意义下的乘法逆元都是它本身,设
p
=
k
i
+
r
(
r
<
i
,
1
<
i
<
p
)
p=ki+r(rp=ki+r(r<i,1<i<p),即
k
i
+
r
ki+r
ki+r 是
p
p
p 的倍数,转化为同余式得
k
i
+
r
≡
0
(
m
o
d
p
)
ki+r\equiv 0(\bmod \ p)
ki+r≡0(mod p)。作一下推导:
将同余式两边乘
i
−
1
,
r
−
1
i^{-1},r^{-1}
i−1,r−1,得
i
−
1
r
−
1
(
k
i
+
r
)
≡
0
(
m
o
d
p
)
i^{-1}r^{-1}(ki+r)\equiv 0(\bmod \ p)
i−1r−1(ki+r)≡0(mod p)
展开得,
k
r
−
1
+
i
−
1
≡
0
(
m
o
d
p
)
kr^{-1}+i^{-1}\equiv 0 (\bmod \ p)
kr−1+i−1≡0(mod p)
移项,得
i
−
1
≡
−
k
r
−
1
(
m
o
d
p
)
①
i^{-1}\equiv-kr^{-1}(\bmod \ p)①
i−1≡−kr−1(mod p)①
由
p
=
k
i
+
r
p=ki+r
p=ki+r,我们可以推导出
k
=
⌊
p
i
⌋
,
r
=
p
m
o
d
i
k=\lfloor \frac{p}{i} \rfloor,r=p \bmod i
k=⌊ip⌋,r=pmodi,结合①式可得
i
−
1
≡
−
⌊
p
i
⌋
×
(
p
m
o
d
i
)
−
1
(
m
o
d
p
)
②
i^{-1}\equiv -\lfloor \frac{p}{i} \rfloor\times (p\bmod i)^{-1}(\bmod \ p)②
i−1≡−⌊ip⌋×(pmodi)−1(mod p)②
余数小于除数,所以 p m o d i < i p \bmod ipmodi<i,所以 p m o d i p\bmod i pmodi 的逆元已经通过递推式②递推出来了,于是我们就可以通过这个式子推出 i − 1 i^{-1} i−1
综上所述,预定义 1 的逆元为 1,再通过②式即可推出
[
1
,
n
]
(
1
≤
n
<
p
)
[1,n](1\leq n [1,n](1≤n<p)
特别注意地,
−
⌊
p
i
⌋
-\lfloor \frac{p}{i} \rfloor
−⌊ip⌋ 为负数,但在模
p
p
p 意义下,显然等价于
−
⌊
p
i
⌋
+
p
-\lfloor \frac{p}{i} \rfloor+p
−⌊ip⌋+p,因此得出最终的递推式为:
i
−
1
≡
(
p
−
⌊
p
i
⌋
)
×
(
p
m
o
d
i
)
−
1
(
m
o
d
p
)
i^{-1}\equiv (p-\lfloor \frac{p}{i} \rfloor)\times (p \bmod i)^{-1}(\bmod \ p)
i−1≡(p−⌊ip⌋)×(pmodi)−1(mod p)
模板链接已经给在上面的,参考代码如下:
#include
#include
#define int long long
using namespace std;
typedef long long LL;
const int N = 3e6 + 10;
int inv[N], n, p;
int read ()
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
signed main()
{
n = read(), p = read();
inv[1] = 1;
printf("1\n");
for (int i = 2; i <= n; i++)
{
inv[i] = (LL)(p - p / i) * inv[p % i] % p;
printf("%d\n", inv[i]);
}
return 0;
}
pause at 2022/8/27,待更新