數論倒數,又稱逆元

數論倒數,又稱逆元(因爲我說習慣逆元了,下面我都說逆元)

數論中的倒數是有特別的意義滴

你以爲a的倒數在數論中還是1/a嗎

(・∀・)哼哼~天真

 

先來引入求餘概念

 

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

 

這時就需要逆元了

 

我們知道

如果

a*x = 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

這樣就把除法,完全轉換爲乘法了 (。・ω・),乘法超容易

 

 

 

 

 

 

 

 

 

正篇開始

 

逆元怎麼求

(忘了說,a和p互質,a纔有關於p的逆元)

 

 

 

 

 

 

方法一:

 

費馬曾經說過:不想當數學家的數學家不是好數學家(( ̄▽ ̄)~*我隨便說的,別當真)

費馬小定理

a^(p-1) ≡1 (mod p)

兩邊同除以a

a^(p-2) ≡1/a (mod p)

什麼(,,• ₃ •,,),這可是數論,還敢寫1/a

應該寫a^(p-2) ≡ inv(a) (mod p)

 

所以inv(a) = a^(p-2) (mod p)

這個用快速冪求一下,複雜度O(logn)(ง •̀_•́)ง 

複製代碼
 1 LL pow_mod(LL a, LL b, LL p){//a的b次方求餘p 
 2     LL ret = 1;
 3     while(b){
 4         if(b & 1) ret = (ret * a) % p;
 5         a = (a * a) % p;
 6         b >>= 1;
 7     }
 8     return ret;
 9 }
10 LL Fermat(LL a, LL p){//費馬求a關於b的逆元 
11         return pow_mod(a, p-2, p);
12 }
複製代碼

 

 

 

 

 

 

 

方法二:

 

要用擴展歐幾里德算法

還記得擴展歐幾里德嗎?(不記得的話,歐幾里得會傷心的(╭ ̄3 ̄)╭♡)

 

a*x + b*y = 1

如果ab互質,有解

 

這個解的x就是a關於b的逆元

y就是b關於a的逆元

爲什麼呢?

 

你看,兩邊同時求餘b

 

a*x % b + b*y % b = 1 % b

a*x % b = 1 % b

a*x = 1 (mod b)

 

你看你看,出現了!!!(/≥▽≤/)

所以x是a關於b的逆元

反之可證明y

 

附上代碼:

複製代碼
 1 #include<cstdio>
 2 typedef long long LL;
 3 void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d){
 4     if (!b) {d = a, x = 1, y = 0;}
 5     else{
 6         ex_gcd(b, a % b, y, x, d);
 7         y -= x * (a / b);
 8     }
 9 }
10 LL inv(LL t, LL p){//如果不存在,返回-1 
11     LL d, x, y;
12     ex_gcd(t, p, x, y, d);
13     return d == 1 ? (x % p + p) % p : -1;
14 }
15 int main(){
16     LL a, p;
17     while(~scanf("%lld%lld", &a, &p)){
18         printf("%lld\n", inv(a, p));
19     }
20 }
複製代碼

 

 

 

 

 

 

 

 

 

 

 

方法三:

當p是個質數的時候有
inv(a) = (p - p / a) * inv(p % a) % p

這爲啥是對的咩?

證明不想看的孩子可以跳過。。。( ̄0  ̄)

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

然後一直遞歸到1爲止,因爲1的逆元就是1

 

代碼:

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

 

 

 

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

遞歸就是上面的寫法,加一個記憶性遞歸,就可以了

遞推這麼寫

複製代碼
 1 #include<cstdio>
 2 const int N = 200000 + 5;
 3 const int MOD = (int)1e9 + 7;
 4 int inv[N];
 5 int init(){
 6     inv[1] = 1;
 7     for(int i = 2; i < N; i ++){
 8         inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;
 9     }
10 }
11 int main(){
12     init();
13 }
複製代碼

 

 

 

 

 

 

又學到新知識了o(*≧▽≦)ツ好開心

 

發佈了122 篇原創文章 · 獲贊 92 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章