在分数取模时总会用到逆元运算
一般只需要求出分母关于取模的逆元即可
然后分子乘以该逆元就是分数取模的结果
在模运算中
(a + b) % p = (a%p + b%p) %p (对)
(a - b) % p = (a%p - b%p) %p (对)
(a * b) % p = (a%p * b%p) %p (对)
(a / b) % p = (a%p / b%p) %p (错)
对于除法的模运算不适合,所以需要定义一个新的东西来求除法模运算,这个东西就是逆元
定义:
((a/b)(mod p)=(a*inv[b])(mod p))或者
(a*b≡1(mod P))
inv[b]就是b的逆元 a就是b的逆元,或者ab或为逆元
则根据定义可知
除以一个数再取模等同于乘以这个数的逆元再取模
但是我们不能直接说一个数的逆元是多少,
应该这么说: 一个数 x 在模 p 的条件下的逆元是多少
比如2 * 3 % 5 = 1,那么3就是2关于5的逆元,或者说2和3关于5互为逆元
(a*inv[b])%p,inv[b]就是a关于p的逆元
条件:a与p互为质数,a才有关于p的逆元
转换除法模运算
根据取模运算
(a + b) % p = (a%p + b%p) %p (对)
(a - b) % p = (a%p - b%p) %p (对)
(a * b) % p = (a%p * b%p) %p (对)
(a / b) % p = (a%p / b%p) %p (错)
(a / b) % p = (a * inv(b) ) % p = (a % p * inv(b) % p) % p
(frac{a}{b}\%p = (a imes inv(b)) \% p = (a \%p imes inv(b)\%p) \%p)
(frac{a}{b}\%p = (a imes inv(b)) \% p = (a \%p imes inv(b)\%p) \%p)
总结
(abequiv 1(mod p))定义a是b在p模意义下的逆元或者b是a在p模意义下的逆元
不存在逆元的情况,b是p的倍数
对于b比p小,或者说p很小时,要注意。化简式子避免求逆元
费马小定理求逆元 - 模必须是质数
时间复杂度(O(logp))
费马小定理:对于质数(p),(a)不是(p)的倍数,均满足(a^{p-1} equiv 1(mod p))
(a^{p-2} imes aequiv 1(mod p))
那么(a^{p-2})就是a在p模意义下的逆元
(inv(a) = a^{p-2} \%p)
ll pow(ll a, ll b, ll p){
ll ans = 1;
while(b){
if(b & 1)ans = ans * a % p;
b >>= 1;
a = a * a % p;
}
return ans;
}
ll inv(ll a, ll p){//求a在p模下的逆元
return pow(a, p - 2, p);
}
扩展欧几里得求逆元 - 模可以不为质数, a和p必须要互质
时间复杂度(O(logp))
(ax + py = 1\ ax \%p + py\%p = 1\%p\ ax\%p = 1\%p\ ax equiv 1(mod p))
如果a和模p互质,那么x就是a在模p意义下的逆元
(inv(a) = x)
void ex_gcd(ll a, ll b, ll &d, ll &x, ll &y){
if(!b){
d = a, x = 1, y = 0;
return;
}
ex_gcd(b,a % b,d,y,x);
y -= x * (a / b);
}
ll inv(ll a, ll p){//求a在p模下的逆元
ll d, x, y;
ex_gcd(a, p, d, x, y);
return d == 1 ? (x % p + p) % p : -1;//d = 1时才有逆元
}
线性求逆元 - 模必须是质数
i模p意义下的逆元(inv(i))为
- 递推版
(inv(i) = -leftlfloorfrac{p}{i} ight floor imes i n v(p \% i) \% p)
void solve(int N, int p){
inv[1] = 1;
for(int i = 2; i <= N; ++ i)
inv[i] = (ll)(p - p / i) * inv[p % i] % p;//加上p为了防止变成负数
}
- 非递推版
线性求数列(a_i)在模p意义下的逆元, 线性求一组非连续的数的逆元
因为(inv(a) = frac{1}{a} \%p)
设(s(n) = prod_{i = n}^1a[i])
设(INV = inv(s(1)))即所有数字乘积的逆元
那么对于
(inv(a_1) = INV imes s[2] \ inv(a_2) = INV imes a_1 imes s[3]\dots \inv(a_i) = INV imes a_1 imes a_2 imes ... imes a_{i-1} imes s[i + 1])
#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
const int N = 1e5 + 5;
void ex_gcd(ll a, ll b, ll &d, ll &x, ll &y){
if(!b){
d = a,x = 1,y = 0;
return;
}
ex_gcd(b, a % b,d, y, x);
y -= x * (a/b);
}
ll inv(ll a, ll p){//求a在p模下的逆元
ll d,x,y;
ex_gcd(a, p, d, x, y);
return d == 1 ? (x % p + p) % p : -1;
}
ll s[N], a[N];
int main(){
int n,p;
scanf("%d%d", &n, &p);
s[n + 1] = 1;
for(int i = 1; i <= n; i++)scanf("%lld", &a[i]);
for(int i = n; i >= 1; i--)s[i] = s[i + 1] * a[i] % p;
ll INV = inv(s[1],p);//所有数乘积的逆元, 只求了一次逆元
for(int i = 1; i <= n; i++){
printf("%lld
", INV * s[i + 1] % p);
INV = INV * a[i] % p;
}
return 0;
}