/* 返回顶部 */

[数论]逆元

模板题:Luogu P3811 【模板】乘法逆元 

逆元类似于倒数,

在($mod$ $p$)意义下,如果$a$*$inv$[$a$]≡ 1 ,那么我们就说 $inv$[$a$]是 $a$ 的逆元,$a$也是$inv$[$a$]的逆元。

前提必须保证$a,p$互质。

当题目要求在$mod$ $p$意义下求$a*b$时,为了避免数据过大引起的麻烦,通常会把它拆成$a$ $mod$ $p*b$ $mod$ $p$。

但是$a/b$却不能这样拆分。这时用逆元把它转化为$a*inv$[$b$],变成乘法,就可以拆分了。

逆元的性质

1.存在唯一性

2.完全积性函数

$inv$[$a$]*$inv$[$b$] ≡ $inv$[$a*b$]

证明:

$a$*$inv$[$a$] ≡ $b$*$inv$[$b$] 1 ($mod$ $p$)

$a$*$inv$[$a$] * $b$*$inv$[$b$] ≡ 1*1 ($mod$ $p$)

($ a $*$b$* ($inv$[$a$]*$inv$[$b$]) ≡ 1 ($mod$ $p$)

3.

$a$*$inv$[$b$] ≡ $a/b$ ($mod$ $p$)

证明:

$b$*$inv$[$b$] ≡ 1 ($mod$ $p$)

同乘$a/b$,得

$a$*$inv$[$b$] ≡ $a/b$ ($mod$ $p$)

 

其他的:

在$(mod$ $n)$意义下,集合{[0,n-1]中存在逆元的数}==集合{[0,n-1]中存在逆元的数的逆元}

因为i < n,inv[i] < n,

a的逆元是b,b∈[0,n-1] ;b的逆元是a,a∈[0,n-1]

所以这些数应该互为逆元。

 

接下来介绍几种逆元的求法。

费马小定理

定理:$x$p-1 ≡ 1 ($mod$ $p$)

证明:(源自数论妙趣——数学女王的盛情款待)

任意取一个质数,比如13。考虑从1到12的一系列整数1,2,3,4,5,6,7,8,9,10,11,12,给这些数都乘上一个与13互质的数,比如3,得到3,6,9,12,15,18,21,24,27,30,33,36。对于模13来说,这些数同余于3,6,9,12,2,5,8,11,1,4,7,10。这些余数实际上就是原来的1,2,3,4,5,6,7,8,9,10,11,12,只是顺序不同而已。

把1,2,3...12统统乘起来,乘积就是12的阶乘12!。把3,6,9,..36也统统乘起来,并且提出公因子3,乘积就是312×12!。对于模13来说,这两个乘积都同余于1,2,3...12系列,尽管顺序不是一一对应,即312×12!≡12!mod 13。两边同时除以12!得312≡1 mod 13。如果用p代替13,用x代替3,就得到费马小定理

$x$p-1≡1 ($mod$ $p$)

由$x$p-1≡1 ($mod$ $p$)

得$x$*$x$p-2≡1 ($mod$ $p$)

$x$p-2是$x$在$mod$ $p$意义下的逆元

注意,这个方法要求$a,p$互质。

int quickpow(int a,int b,int p){
    int ans = 1;
    int base = a;
    while(b){
        if(b&1)ans = ans*base % p;
        base = base*base % p;
        b >>= 1;
    }
    return ans%p;
}

int main(){
    scanf("%d%d",&n,&p);
    for(int i = 1;i <= n;i++)
        printf("%d\n",quickpow(i,p-2,p));
    return 0;
}

 

扩展欧几里得

求$a*x≡1$ ($mod$ $p$),等同于

$a*x+p*y≡1$

我们知道,$exgcd$可求$a*x + b*y≡gcd(a,b)$

而这里的$a,p$互质,刚好有$gcd(a,p)=1$

所以求出$exgcd(a,p,x,y)$,$inv[a] = x$

这个方法要比费马小定理快一点。

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

int main(){
    scanf("%d%d",&n,&p);
    for(int i = 1;i <= n;i++){
        exgcd(i,p,x,y);
        printf("%d\n",(x%p+p)%p);
    }
    return 0;
}

 

线性推逆元

公式:inv[i]=inv[p%i]*(p-p/i)%p

证明:

令$s=p/i,t=p$ $mod$ $i$,

则$s*i+t=p$

$s*i+t≡0$ ($mod$ $p$)

$t≡-s*i$ ($mod$ $p$)

两边同除$t*i$,得

$t/(t*i)≡-s*i/(t*i)$ ($mod$ $p$)

$inv[i]≡-s*inv[t]$ ($mod$ $p$)

将$s,t$的值带入,则有

$inv[i]≡(-p/i)*inv[p$ $mod$ $i]$ ($mod$ $p$)

将$-p/i$处理成正数,即$-p/i=p-p/i$

整理,得$inv[i]≡inv[p$ $mod$ $i]*(p-p/i)$ ($mod$ $p$)

这个方法很好,适用于要求很多逆元的情况。

inv[0] = inv[1] = 1;
for(int i = 2; i <= n; i++)
    inv[i] = inv[p%i]*(p-p/i)%p;

 

posted @ 2019-03-30 09:43  Mogeko  阅读(407)  评论(0编辑  收藏  举报