逆元

在分数取模时总会用到逆元运算
一般只需要求出分母关于取模的逆元即可
然后分子乘以该逆元就是分数取模的结果

在模运算中

(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 \times inv(b)) \% p = (a \%p \times inv(b)\%p) \%p\)

\(\frac{a}{b}\%p = (a \times inv(b)) \% p = (a \%p \times inv(b)\%p) \%p\)

总结

\(ab\equiv 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}\times a\equiv 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)\)

  1. 递推版

\(inv(i) = -\left\lfloor\frac{p}{i}\right\rfloor \times 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为了防止变成负数
}
  1. 非递推版
    线性求数列\(a_i\)在模p意义下的逆元, 线性求一组非连续的数的逆元

因为\(inv(a) = \frac{1}{a} \%p\)
\(s(n) = \prod_{i = n}^1a[i]\)
\(INV = inv(s(1))\)即所有数字乘积的逆元

那么对于
\(inv(a_1) = INV \times s[2] \\ inv(a_2) = INV \times a_1 \times s[3]\\\dots \\inv(a_i) = INV \times a_1\times a_2\times ...\times a_{i-1} \times 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\n", INV * s[i + 1] % p);
        INV = INV * a[i] % p;
    }
    return 0;
}
posted @ 2019-08-12 20:25  Emcikem  阅读(477)  评论(0编辑  收藏  举报