乘法逆元
一、逆元定义
若a * x ≡ 1 (mod b),且a和b互质
那么就能定义:x为a的逆元,记为a-1
所以诚x为a在mod b的意义下的倒数
所以对于a/b(mod p)
就可以求b在mod p下的逆元,然后乘上a,再mod p。
二、适用范围
乘法逆元一般用于求 a/b (mod p) 的值
(p通常为质数,也可以不为质数)
三、求解逆元的四种方法
拓展欧几里得
费马小定理
线性递推
阶乘
注:
前两个算法(拓展欧几里得 和 费马小定理)适合求解单个值的逆元 [拓展欧几里得似乎比费马小定理还要好一些]
后两个算法(线性递推 和 阶乘)适合一串数对于一个mod p的逆元 [线性递推大概要比阶乘好一些吧]
四、算法
1.拓展欧几里得
适用范围:a和p互质 p可以不是质数
利用拓展欧几里得求解同余方程 a * x ≡ c (mod b) 当c = 1的情况
转化成解 a * x + b * y = 1 这个方程
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll n,p;
void exgcd(ll a,ll b,ll &x,ll &y)
{
if(!b)
x = 1,y = 0;
else
exgcd(b,a%b,y,x),y -= a/b * x;
}
int main()
{
scanf("%lld%lld",&n,&p);
ll x,y;
for(int i = 1; i <= n; i++)
{
x = 0,y = 0;
exgcd(i,p,x,y);
x = (x % p + p)%p;
printf("%lld\n",x);
}
return 0;
}
(按洛谷板子题写的)(tle两个点)
2.费马小定理
咕咕咕(自己的一篇极简题解)
适用范围:a和p互质,p为素数。
根据费马小定理
bp - 1 ≡ 1(mod p),即 b * bp - 2 ≡ 1 (mod p)
因此,当模数p为质数时,bp - 2即为b的乘法逆元
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll n,p;
ll qpow(ll a,ll b)
{
a %= p;
ll sum = 1;
while(b)
{
if(b%2 == 0)
a = (a * a)%p,b/=2;
else
sum = (sum * a)%p,b--;
}
return sum % p;
}
int main()
{
scanf("%lld%lld",&n,&p);
for(int i = 1; i <= n - 1; i++)
printf("%lld\n",qpow(i,p - 2));
printf("%lld",qpow(n,p - 2));
return 0;
}
(tle了两个点)
3.线性递推
首先有一个,1-1 ≡ 1(mod p)
然后设p = k * i + r ( 1 < r < i < p)
也就是k是 p / i 的商,r是余数
再讲这个式子放到(mod p)意义下就会得到
k * i + r ≡ 0
然后乘上i-1 , r-1 就可以得到
k * r-1 + i-1 ≡ 0 (mod p)
i-1 ≡ -k * r-1 (mod p)
i-1 ≡ -(p / i) * (p mod i)-1(mod p)
#include<cstdio>
#define ll long long
using namespace std;
int main()
{
ll n,p;
ll inv[3000005];
scanf("%lld%lld",&n,&p);
inv[1] = 1;
for(ll i = 2;i <= n;i++)
inv[i] = (p - p / i) * inv[p % i] % p;
for(ll i = 1;i <= n;i++)
printf("%lld\n",inv[i]);
return 0;
}
4.阶乘
inv[i + 1] = 1/ (i + 1)!
inv[i + 1] * (i + 1) = 1 / i! = inv[i]
所以我们可以求出n!的逆元,然后逆推,就可以求求出1...n!所有的逆元了
逆推式为
inv[ i + 1 ] * (i + 1)= inv[ i ]
所以我们可以求出任意i,i!,1/i! 的取值了
然后这个也可以导出 1 / i (mod p) 的取值
也就是 1 / i ! * ( i - 1) ! = 1 / i ( mod p )
#include<cstdio>
#define ll long long
using namespace std;
ll n,p;
ll inv[3000005],f[3000005];
ll qpow(ll a,ll b)
{
ll sum = 1;
while(b)
{
if(b%2 == 1)
sum = (sum * a) % p , b--;
else
a = (a * a) % p , b /= 2;
}
return sum;
}
int main()
{
scanf("%lld%lld",&n,&p);
f[0] = 1;
for(int i = 1; i <= n; i++)
f[i] = (f[i - 1] *i) %p;
ll last = qpow(f[n],p - 2);
ll k;
for(ll i = n; i >= 1; i--)
{
k = (last * i) % p;
inv[i] = last * f[i - 1] % p;
last = k;
}
for(int i = 1;i <= n;i++)
printf("%lld\n",inv[i]);
return 0;
}