乘法逆元的四種求法(拓展歐幾里得、費馬小定理、遞歸、遞推)

前言

逆元:如果ax1(mod p)a*x\equiv1(mod\ p),且a與p互質,則稱x是a關於p的逆元。
對於這個概念和倒數有本質的區別,因爲除法不能將mod數化進去。引用一個例子:

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

爲什麼除法錯的?
證明是對的難,證明錯的只要舉一個反例
(100/50)%20 = 2 ≠ (100%20) / (50%20) %20 = 0

對於一些題目,我們必須在中間過程中進行求餘,否則數字太大,電腦存不下,那如果這個算式中出現除法,我們是不是對這個算式就無法計算了呢?

答案當然是 NO (>o<)
這時就需要逆元了

我們知道:
如果
ax = 1
那麼x是a的倒數,x = 1/a
但是a如果不是1,那麼x就是小數
那數論中,大部分情況都有求餘,所以現在問題變了
a
x = 1 (mod p)
那麼x一定等於1/a嗎?

不一定
所以這時候,我們就把x看成a的倒數,只不過加了一個求餘條件,所以x叫做 a關於p的逆元
比如2 * 3 % 5 = 1,那麼3就是2關於5的逆元,或者說2和3關於5互爲逆元
這裏3的效果是不是跟1/2的效果一樣,所以才叫數論倒數

a的逆元,我們用inv(a)來表示

那麼(a / b) % p = (a * inv(b) ) % p = (a % p * inv(b) % p) % p
這樣就把除法,完全轉換爲乘法了。

以上的例子摘自這篇博客,強推,淺顯易懂!


對於法三用到如下公式變形:

證明:
設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

法四就是法三的記憶化版或者說是遞推版

Code

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;

//拓展歐幾里得
LL exgcd(LL A, LL B, LL &x, LL &y) {
  if (B == 0) {
    x = 1;
    y = 0;
    return A;
  }
  LL gcdnum = exgcd(B, A % B, x, y);
  LL tmp = y;
  y = x - A/B*y; //算上來的x,y計算出新的x,y
  x = tmp;
  return gcdnum;
}

//中速乘  : 對於長整型的快速乘要利用到80爲的long double的特性O(1)  本算法複雜度O(logN)
LL qmul(LL a, LL b, LL mod) {
  LL ans = 0;
  a %= mod;
  while (b) {
    if (b&1) {
      ans = (ans + a) % mod;
    }
    a = (a << 1) % mod;
    b >>= 1;
  }
  return ans;
}

//快速冪
LL qpow(LL a, LL b, LL mod) {
  LL ans = 1;
  a %= mod;
  while (b) {
    if (b & 1) {
      ans = qmul(ans, a, mod);
    }
    a = qmul(a, a, mod);
    b >>= 1;
  }
  return ans;
}

//逆元的三種求法求法:

//法一:根據費馬小定理轉換 O(logP)
LL inv_1(LL a, LL p) {  //a關於p的逆元(數論倒數)
  return qpow(a, p-2, p);
}

//法二:根據拓展歐幾里得求逆元
LL inv_2(LL a, LL p) {
  LL x, y;
  if (exgcd(a, p, x, y) != 1) return -1; //不滿足a p 互質
  return (x % p + p) % p; //變爲正的
}

//法三:遞歸版 (原理就是一個式子化簡  至於怎麼構造還不會,待補  參考:https://www.cnblogs.com/linyujun/p/5194184.html)
LL inv_3(LL a, LL p) {
  //求a關於p的逆元,注意:a要小於p,最好傳參前先把a%p一下
  return a == 1 ? 1 : (p - p / a) * inv_3(p % a, p) % p;
}

const int maxn = (int)2e5 + 5;
const int MOD = (int)1e9 + 7;
int inv[maxn];
void inv_4(){ //法三遞推版 O(n)
  inv[1] = 1;
  for(int i = 2; i < maxn; i ++){
      inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;
  }
}

int main() {
  cout << "inv_1 : " << inv_1(100, MOD) << endl;
  cout << "inv_2 : " << inv_2(100, MOD) << endl;
  cout << "inv_3 : " << inv_3(100, MOD) << endl;
  inv_4();
  cout << "inv_4 : " << inv[100] << endl;
  return 0;
}


關於費馬小定理:假如p是質數,且gcd(a,p)=1(a和p互質),那麼 a^(p-1) ≡ 1(mod p),即 ( a^(p-1) )%p = 1。

  • 可以用這個定理快速求得一個大數的餘數。例如:
    2100 % 13= ?欲求:2^{100}\ \%\ 13=\ ?
    21321311(mod 13)因爲2與13互質,故根據費馬小定理有:2^{13-1}\equiv1(mod\ 13)
    212 % 13=1即:2^{12}\ \%\ 13 = 1
    2100=(212)824又:2^{100}=(2^{12})^{8}*2^4
    2100 % 13=(212)824 % 13=((212)8 % 13)(24 % 13) % 13=((212 % 13)8 % 13)(3) % 13=3所以,2^{100}\ \%\ 13=(2^{12})^{8}*2^4\ \%\ 13=((2^{12})^{8}\ \%\ 13)*(2^4\ \%\ 13)\ \%\ 13=((2^{12}\ \%\ 13)^8\ \%\ 13)*(3)\ \%\ 13=3
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章