求逆元的方法及模板


數論倒數,又稱逆元,在數論中很有意義。在數論中取模運算十分普遍,我們知道取模運算有如下性質:

(a +  b) % p = (a%p +  b%p) %p  (對)

(a  -  b) % p = (a%p  -  b%p + p) %p  (對)

(a  *  b) % p = (a%p *  b%p) %p  (對)

(a  /  b) % p = (a%p  /  b%p) %p  (錯)

對除法操作來說,就無法直接進行取模運算了,那么應該怎樣做呢,就需要用到逆元了。若a*x=1(mod p),其中gcd(a,p)=1,即a,p互質,則稱x為a關於p的逆元,或者a,x關於p互為逆元。稱a的逆元為inv(a),這樣就將除法取模運算轉換成(a/b)%p=(a*inv(b))%p=(a%p*inv(b)%p)%p。

逆元怎么求:

方法一——費馬小定理求逆元

(前提是p為質數,否則不能用)根據費馬小定理,當gcd(a,p)=1時,有a^(p-1)=1 (mod p),即a*a^(p-2)=1 (mod p),即a關於p的逆元為a^(p-2),用快速冪即可求。

模板:

LL pow_mod(LL a, LL b, LL p){//a的b次方求余p 
    LL ret = 1;
    while(b){
        if(b & 1) ret = (ret * a) % p;
        a = (a * a) % p;
        b >>= 1;
    }
    return ret;
}
LL Fermat(LL a, LL p){//費馬求a關於b的逆元 
        return pow_mod(a, p-2, p);
}

 

方法二——擴展歐基里德求逆元:

若gcd(a,b)=1,則a*x+b*y=1有解,其解的x即為a關於b的逆元,y為b關於a的逆元。

  證明:a*x+b*y=1  ->   a*x%b+b*y%b=1%b  ->  a*x%b=1%b  ->  a*x=1 (mod b),即x為a關於b的逆元,同理可證y。

模板:

#include<cstdio>
typedef long long LL;
void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d){
    if (!b) {d = a, x = 1, y = 0;}
    else{
        ex_gcd(b, a % b, y, x, d);
        y -= x * (a / b);
    }
}
LL inv(LL t, LL p){//如果不存在,返回-1 
    LL d, x, y;
    ex_gcd(t, p, x, y, d);
    return d == 1 ? (x % p + p) % p : -1;
}
int main(){
    LL a, p;
    while(~scanf("%lld%lld", &a, &p)){
        printf("%lld\n", inv(a, p));
    }
}

 

方法三:

一個常見的問題:已知b|a(b能整除a),求a/b mod m,我們發現費馬小定理和擴展歐基里德求逆元都有局限,需要gcd(a,p)=1。實際上還有一種通用的求逆元的方法,適合所有情況。即a/b % m=a%(b*m)/b。證明:

 

 

方法四:

(前提為p是質數,否則不能用)inv(a) = (p - p / a) * inv(p % a) % p,然后一直遞歸到1即可,因為1的逆元是1。

  證明:
    設x = p % a,y = p / a
    於是有 x + y * a = p
    (x + y * a) % p = 0
    移項得 x % p = (-y) * a % p
    x * inv(a) % p = (-y) % p
    inv(a) = (p - y) * inv(x) % p
    於是 inv(a) = (p - p / a) * inv(p % a) % p

這個方法不限於求單個逆元,比前兩個好,它可以在O(n)的復雜度內算出n個數的逆元。

模板:

#include<cstdio>
typedef long long LL;
LL inv(LL t, LL p) {//求t關於p的逆元,注意:t要小於p,最好傳參前先把t%p一下 
    return t == 1 ? 1 : (p - p / t) * inv(p % t, p) % p;
}
int main(){
    LL a, p;
    while(~scanf("%lld%lld", &a, &p)){
        printf("%lld\n", inv(a%p, p));
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM