ACM數論之旅6---數論倒數,又稱逆元(我整我的都倒了( ̄﹏ ̄))

 

數論倒數,又稱逆元(由於我說習慣逆元了,下面我都說逆元)算法

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

你覺得a的倒數在數論中仍是1/a嗎code

(・∀・)哼哼~天真blog

 

先來引入求餘概念遞歸

 

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

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

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

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

 

爲何除法錯的擴展

證實是對的難,證實錯的只要舉一個反例

(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(*≧▽≦)ツ好開心

相關文章
相關標籤/搜索