• 逆元


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

    在模运算中

    (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))

    1. 递推版

    (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为了防止变成负数
    }
    
    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 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;
    }
    
  • 相关阅读:
    Spring Boot中的JSON技术
    Spring Boot中编写单元测试
    如何保证事务方法的幂等
    定时重试线程池
    多线程导致事务失效-记一次性能优化
    自己实现一个简单的数据库事务
    服务器错误码国际化
    spring自定义自动配置注解
    springboot中如何启动tomcat
    用grep来查询日志
  • 原文地址:https://www.cnblogs.com/Emcikem/p/11342346.html
Copyright © 2020-2023  润新知