逆元
什么是逆元
在数论中,如果 ab≡1(modp) ,我们就说 a 和 b 在模 p 意义下互为乘法逆元,记作 a=inv(b)。
逆元有什么用呢?
我们常常遇到一些题目要求结果对一个大质数 p 取模,这是因为答案很大,出题人为了不麻烦大家写高精,就采取这样的方法。加减法和乘法对取模运算都是封闭的,所以你可以处处取模来避免溢出。
例如:
(12+3) % 10=5
12 % 10=2
3 % 10=3
(2+3) % 10=5
但遇到除法时,就麻烦了:
(12 ÷ 3) % 10=4
12 % 10=2
3 % 10=3
(2÷3) % 10= ? ? ?
为了解决模意义下的除法问题,我们引入了逆元。
inv(a) 其实可以看作模 p 意义下的 a1,也称为模 p 意义下的 a−1。
即 a×inv(a)≡1(modp)
那么在模 p 意义下, ba 就可以变形为 a×inv(b)(modp)。
实际上在模 10 意义下 inv(3)=7 ,所以上面的式子可以这样计算:
(12÷3) % 10=4
12 % 10=2
3 % 10=3
(2×inv(3)) % 10=4
这里介绍三种计算逆元的方法:扩展欧几里得,费马小定理,线性递推。
扩展欧几里得
这个方法十分容易理解,而且对于单个查找效率似乎也还不错,比后面要介绍的大部分方法都要快(尤其对于 modp 比较大的时候)。
这个就是利用拓欧求解线性同余方程 a∗x≡c(modb) 的 c=1 的情况。我们就可以转化为解 a∗x+b∗y=1 这个方程。
先简单讲解一下欧几里德算法:
GCD(x,y)=GCD(x,y-x)
证明:
-
设 z∣x,z∣y,则 z∣(y−x)。
-
设 z 不是 x 的因子,则 z 不是 x,y−x 的公因子。
-
设 z∣x,z 不是 y 的因子,则 z 不是 x,y−x 的公因子。
-
证毕。
GCD(x,y)=GCD(y,x−y)=GCD(y,x−by)=GCD(y,x % y)。
int GCD(int x, int y)
{
return y == 0 ? x : GCD(y, x % y);
}
这里简单讲解一下扩欧:
对于三个自然数 a,b,c,求解 ap+bq=c 的 (x,y) 的整数解。
首先我们要判断是否存在解,对于这个这个存在整数解的充分条件是 gcd(a,b)∣c。
也就是说 c 为 a,b 最大公因数的一个倍数。
但此时只需要求 c=gcd(a,b) 的情况就行了。
首先,解一定存在。
其次,因为 GCD(a,b)=GCD(b,a % b),所以 p∗a+q∗b=GCD(a,b)=GCD(b,a % b)=p∗b+q∗a % b=p∗b+q∗(a−a/b % b)=q∗a+(p−a/b∗q)∗b。
根据前面的结论:a 和 b 都在减少,当 b 减到 0 是,就可以得出 p=1,q=0。
然后递归回去就可以求出最终的 p 和 q 了。
然后详细的求解过程就看这篇文章吧,点这里。
这个做法还有个好处在于,当 a⊥p(互质),但 p 不是质数的时候也可以使用。
代码比较简单:
long long exgcd(long long a, long long b, long long &x, long long &y)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
long long d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return d;
}
long long inv(long long a, long long p)
{
long long x, y;
if (exgcd(a, p, x, y) != 1)
return -1;
return (x % p + p) % p;
}
费马小定理
费马小定理是数论里的重要定理,叙述如下:
若 p 是质数,且 gcd(a,p)=1 ,则有 ap−1≡1(modp)。
费马小定理是欧拉定理的特殊情况。
欧拉定理如下:
如果 x 和 n 互质,则 xϕ(n)≡1(modn)。
ϕ(x) 表示与 x 互质,且在 1∼(x−1) 中的数的个数。
证明:
-
设 a1,a2,∼,aϕ(n) 为与 n 互质,且在 1∼(n−1) 中的数的个数。
-
将序列 a 所有数乘上 x,则有 xa1,xa2,∼,xaϕ(n)。
-
可得 xa1∗xa2∗∼∗xaϕ(n)≡a1∗a2∗∼∗aϕ(n)(modn)。
-
化简得 xϕ(n)∗a1∗a2∗∼∗aϕ(n)≡a1∗a2∗∼∗aϕ(n)(modn)。
-
两边同除以 a1∗a2∗∼∗aϕ(n),得 xϕ(n)≡1(modn)。
-
证毕。
考虑特殊情况,即 n 为质数,则 ϕ(n)=n−1,有 xn−1≡1(mod()n),同时除以 x 得 xn−2≡x−1(mod()n)。
上述即为费马小定理。
从逆元的定义推导,可得 a×inv(a)≡1≡ap−1(modp),于是有 inv(a)≡ap−2(modp)。
于是对 ap−2 算一下快速幂就好了。
注意这个方法只对 p 是质数的情形有效。
inline long long Pow(long long a, long long n, long long p)
{
long long ans = 1;
while (n)
{
if (n & 1)
ans = ans % p * a % p;
a = a % p * a % p;
n >>= 1;
}
return ans;
}
inline long long inv(long long a, long long p)
{
return Pow(a, p - 2, p);
}
线性递推
例题
给定 n,p 求 1∼n 中所有整数在模 p 意义下的乘法逆元。
输入保证 p 为质数,且 1≤n≤3×106,n<p<20000528。
如果我们要求一系列的乘法逆元,而且数据范围是 1≤n≤3×106,常规方法是行不通的。这里介绍逆元的线性递推求法(需保证 p 是质数)。
设 p=aq+r, 即 q=⌊ap⌋,r=pmoda。
在模 p 意义下,有 aq+r≡0(modp)。
因为 inv(x) 可看作模 p 意义下的 x1,
所以移项整理得 a=−r×inv(q)(modp)。
则 inv(a)=−q×inv(r)(modp)
即 inv(a)=−⌊ap⌋×inv(pmoda)(modp)
#include <bits/stdc++.h>
#define _ (long long)3e6 + 5
using namespace std;
long long Inv[_];
inline long long mod(long long a, long long p)
{
return (a % p + p) % p;
}
long long inv(long long a, long long p)
{
if (Inv[a] || a == 0)
return Inv[a];
Inv[a] = mod(-p / a * inv(p % a, p), p);
return Inv[a];
}
long long n, p;
signed main()
{
scanf("%lld%lld", &n, &p);
Inv[1] = 1;
for (int i = 1; i <= n; ++i)
{
printf("%lld\n", inv(i, p));
}
return 0;
}