取模意义下除法的处理&&乘法逆元 理解与求法

乘法逆元的意义**

取余下,有些除号要变逆元(/b = *b^(-1)),有些除号可以消去 ( a /b *b =a) **

逆元 记作 ..^(-1) 之后直接当幂计算了。**

不确定的性质,尝试能否力所能及地举几个反例

(a / b) % p = (a%p / b%p) %p (错)可见取模下若有除法,结果可能不太对。

为了实现取模意义下的除法/计算取模意义下的除法算式;对于一些题目,我们必须在中间过程中进行求余,否则数字太大,电脑存不下,那如果这个算式中出现除法,

取模下除号可看做分数。分数为整数还好,否则除号就得看作除法(取模是整数运算,没有对小数的相关定义)。为整数时分数与除法的结果都一样,若无逆元则只能看做分数**

参考文献:

https://baijiahao.baidu.com/s?id=1609032096408414934&wfr=spider&for=pc

https://www.luogu.com.cn/blog/1239004072Angel/post-shuo-xue-sheng-fa-ni-yuan

逆元:

  逆元素(简称逆元)是指一个可以取消另一给定元素运算的元素,如加法中的加法逆元(相反数)和乘法中的倒数。简单来说,A的逆元就是能抵消A的影响的元素。

数论中的乘法逆元:

  定义:理解也很好,a^(-1) *a =1,即逆元消除了乘a的影响。

  作用/意义:实现模意义下的大部分除法。(除法就是求另一个因数,这样乘上逆元在消除除数就好了)

    结合除法的定义(https://baike.baidu.com/item/%E9%99%A4%E6%B3%95/6280598?fr=aladdin),模意义下的a/b,即为求a在模意义下除了b的另一个因数(设为c),即求b*c≡a (mod p)的c,这时给a乘上b的逆元就消去了乘b的影响,结果为c,即b*c *b^(-1)≡a *b^(-1) ≡c(mod p)。即,模意义下除以一个数就等价于乘这个数的乘法逆元。还说明,到了剩余系中,四则运算的意义较平时会有些微的差别。

  

取模意义下的除法的实现:(常用方法与保底方法)

  取模意义下的除法的本质是求取模意义下的另一个因数。理解本质后就方便干更多事了。

  但有时我们发现并非所有模意义下除法都要必须转化为乘法逆元去做,而且有些除法还不能用乘法逆元。比如6/2 (mod p):可以求2的逆元x,在算6*x=y(逆元算法);但是6/2是可整除的,可直接得出6除了2的另一个因数(即为结果)为3(直接算)。可以证明两种算法的结果在[0,p)内是相同的。到了这时就有了一个想法,尝试验证当a能被b整除时,商模p后(设为y)也可作为模意义下a/b的答案:

    首先说结论:直接算和逆元算法这两种算法做取模下的除法的结果都正确且在p的范围内唯一。y也是满足模意义下除法的定义的(显然y*b≡a (mod p)),设通过逆元的方法得出a/b ≡ c (mod p),再对c模p(一般要求的答案都要在p的范围内)。此时有式子:b*y ≡ b*c ≡ a (mod p),即b*(y-c) ≡ 0 (mod p)。

      当b,p互质时,逆元c才存在(下文有讲逆元的存在性),此时p|(y-c),若y,c都小于p大于0的话(可通过模p、小于0时加p解决,本质不变),可知y=c,计算除法的即两算法都可用,且结果一样;

      接上文,当b,p不互质时,逆元c不存在,a/b只能用直接算。

  总之取模下的除法可以直接算(能整除)也可以用逆元算(存在逆元)。既不能整除也不存在逆元的话,还有适用性最广的算法:扩展欧几里得。

  扩展欧几里得求 除法:由前文除法的定义,求a/b (mod p),即为求解方程b x ≡ a (mod p)。除法有解的充要条件为 gcd(b,p)|a (也是同余方程、转化的不定方程有解的条件)。由于复杂度为O log n,不如直接算(O 1)和逆元可快速(比 log n 更快)计算时用逆元算,也就在上两种处理除法的算法条件都不满足时才适用。

  

乘法逆元的性质:

  1、存在唯一性:对 a 来说,若它有逆元,则在p范围内一定只有一个逆元。

    简单证明:

    

    a*k≡ 0 (mod p)。a有逆元,则a,p互质,则p|k 。a'和a''都在p范围内,则k=0,矛盾,故假设不成立。

  

  

  2、完全积性函数:(为了接下来方便,我们把 a 的逆元表示为 inv[a] )两个数的逆元的积等于这两个数积的逆元, inv[a]*inv[b]=inv[a*b] 。(逆元的积 能抵消原数的积)

    简单证明:(a*b)* (a^(-1) * b^(-1))=1,所以(a^(-1) * b^(-1))是积的逆元,同时它又是逆元的积。

  

  3、存在的充要条件:a,p互质

乘法逆元的求法:

  1、拓展欧几里得求逆元:(解等价的不定方程)

      

      再解出这个不定方程的x就好。

    

    不定方程与同余方程等价,也说明了同余方程有解(有逆元)的充要条件与不定方程有解的一致。

    时间复杂度:log p

    代码:

    

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

long long a, b;

int exgcd(long long a, long long b, long long &x, long long &y)
{
    if (b == 0)
    {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main()
{
    scanf("%lld%lld", &a, &b);
    long long x = 0, y = 0;
    exgcd(a, b, x, y);
    printf("%lld", (x % b + b) % b);//这里防止出现负数
    return 0;
}

   2、欧拉定理求逆元:(求一个快速幂。指数减几各有意义,记准指数就好)

      

       a^φ(p) ≡ a * a^(φ(p)-1)  ≡ 1 (mod p),a^(φ(p)-1)即为a的逆元。

      O sqrt(n)枚举因数,再套公式:

      

      1- (1/pi)处理时变形为  /pi * (pi - 1) ,因为pi|n,不会出现有小数的情况。然后再做快速幂。

    时间复杂度 log φ(p) + sqrt(n)(时间复杂度是忽略“-1” 等 的常数)(求欧拉函数的复杂度(sqrt(n))在某些情况可以被优化,可见:欧拉函数和线性筛

    代码:

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

long long n, p;

long long phi(long long x)//求x的欧拉函数
{
    long long res = x, tmp = x;//初始化答案为x
    for (int i = 2; i * i <= tmp; ++i)
    {
        if (x % i == 0)
        {
            res = (res / i) % p * ((i - 1) % p) % p;//找到x的一个质因子,计算其对答案的贡献
            while (x % i == 0) x /= i;//统计完答案后,除去该质因子
        }
    }
    if (x > 1) res = (res / x) % p * ((x - 1) % p) % p;//防止漏筛质因子
    return res;
}

long long power(long long a, long long b)//快速幂
{
    long long res = 1;
    while (b)
    {
        if (b & 1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}

int main()
{
    scanf("%lld%lld", &n, &p);
    long long tmp = phi(p) - 1;
    printf("%lld", power(n, tmp));
    return 0;
}

  3、费马小定理求逆元:(欧拉定理的特殊情况,不用求欧拉函数了。记准指数)

    

    a^(p-1)≡ a * a^(p-2) ≡ 1 (mod p)  。  a^(p-2) 即为逆元。做一个 快速幂就好。

    时间复杂度 log p

    由于大多数题目的模数都为质数,所以很泛用。

    核心代码:

inline int power(int a, int b)//快速幂
{
    int res = 1;
    while (b)
    {
        if (b & 1) res = 1ll * res * a % p;
        a = 1ll * a * a % p;
        b >>= 1;
    }
    return res;
}

int main()
{
    int a, b;
    cin>>a>>b;
        printf("%d", (int)(1ll * a * power(b, p - 2) % p));
    return 0;
}

以上代码都没有逆元存在性的判定,根据具体题目情况,判定该加就加。

  4、线性求逆:(让i的逆元出现在公式,按照公式递推)

    要求p 为质数。

      

       (边界条件)

      设当前要求i的逆元。对p进行带余除法,p=ki+r(r<i,1<i<p),将其转化为同余式会得到:

      (希望让i^(-1)单独出现在一边,要消系数。p%p=0,也可通过这个式子来探索性质。)

       

       

       

    时间复杂度:O n    这里n实际是值域,数值范围很大时就不好用了。

    代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 3e6 + 10; 
long long inv[maxn], n, p;

int main()
{
    scanf("%lld%lld", &n, &p);
    inv[0] = 0, inv[1] = 1;//初始化1的逆元为1
    printf("%lld
", inv[1]);
    for (int i = 2; i <= n; ++i)
    {
        inv[i] = (p - p / i) * inv[p % i] % p;//上文中的式子
        printf("%lld
", inv[i]);
    }
    return 0;
}

  5、离线线性求逆:(算出整体逆元后到着一步步算出个体)

    线性时间求n个给定较大的整数a1,a2,...,an模p意义下的逆元。一般保证全部有逆元,或说 p是质数且a中无p的倍数。

    由于值域很大,不能用普通的线性求逆。

    尝试前缀积。由逆元的完全积性,前缀积的逆元等于逆元的前缀积

      设pre[i]为前缀积,即为 a1*a2*...*ai;inv_pre[i]为pre[i]的逆元,即为 a1^(-1) *a2^(-1) *...*ai^(-1) ;inv[i]为ai的逆元

      可以发现几个关系:inv[i]=inv_pre[i]*pre[i-1]  inv_pre[i-1]=inv_pre[i]*ai。

      这样,算出所有前缀积后,可以先用某个log算法算一个inv_pre[n],然后逐渐算出inv[n],inv_pre[n-1],inv[n-1],....就得到答案了。

    

    了解算法后应该能看出一个问题。前缀积中一旦有一个a没有逆元,即不与p互质,算法就无法算逆元了。所以用这种算法的话,p一般都是质数且a中无p的倍数,否则要将没有逆元的a挑出,但这样就难用线性算法办了。

    巧妙利用了逆元对原数的消去作用与完全积性,算出整体后再逐一消去算出个体。

    时间复杂度 O (n +log p)

    核心代码:

    pre[0] = 1;//初始化
    for (register int i = 1; i <= n; ++i)
    {
        read(a[i]);
        pre[i] = 1ll * pre[i - 1] * a[i] % p;//计算前缀积
    }
    inv_pre[n] = power(pre[n], p - 2, p);//求出前缀积的逆元
    for (register int i = n; i > 0; --i)//递推求逆元
    {
        inv[i] = 1ll * pre[i - 1] * inv_pre[i] % p;
        inv_pre[i - 1] = 1ll * inv_pre[i] * a[i] % p;
    }

    

  

     

      

  

原文地址:https://www.cnblogs.com/InductiveSorting-QYF/p/15362450.html