逆元~(乘法逆元及其应用)
數論倒數,又稱逆元(因為我說習慣逆元了,下面我都說逆元)
先來引入求余概念
(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
對于一些題目,我們必須在中間過程中進行求余,否則數字太大,電腦存不下,那如果這個算式中出現除法,會損失精度,導致答案變小。
?
這時就需要逆元了
我們知道,如果a*x = 1那么x是a的倒數,x = 1/a但是a如果不是1,那么x就是小數那數論中,大部分情況都有求余,所以現在問題變了a*x ?= 1 (mod p)那么x一定等于1/a嗎?不一定。所以這時候,我們就把x看成a的倒數,只不過加了一個求余條件,所以x叫做 ? ?a關于p的逆元。這時就需要逆元了
我們知道,如果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 * k ≡ 1 (mod p)?的k?叫做?a關于p的乘法逆元。另一種表達方法是?k ≡ a-1?(mod p)
應用:
我們知道(a+b)%p=(a%p+b%p)%p
(a*b)%p=(a%p)*(b%p)%p
而求(a/b)%p時,可能會因為a是一個很大的數,不能直接算出來,卻又不能(a/b)%p=(a%p/b%p)%p。
但是可以通過?k ≡ b-1?(mod p)?
a / b
= a * b-1?(mod p?)
=?(a?mod p?) * (b-1?mod p?)??mod p
?
=?(a?mod p?) * (k?mod p?)??mod p
= a * k mod p
轉換為a*k % p 的問題,然后a是一個加減乘的式子,就可以用上面兩個取余公式了。
正篇開始:
逆元怎么求?
?
方法一:(滿足p為質數)
費馬曾經說過:不想當數學家的數學家不是好數學家(( ̄▽ ̄)~*我隨便說的,別當真)
費馬小定理(因為費馬小定理的前提 就是 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)(? ??_??)??
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); }方法二:(p可以不是質數)
?
要用擴展歐幾里德算法
還記得擴展歐幾里德嗎?(不記得的話,歐幾里得會傷心的(╭ ̄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
#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));} }方法三:(當p為質數時,可以用下面的方法在O(n)的復雜度下求出多個數關于p的逆元)
當p是個質數的時候有
inv(a) = (p - p / a) * inv(p % a) % 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
?
然后一直遞歸到1為止,因為1的逆元就是1
代碼:
#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));} }這個方法不限于求單個逆元,比前兩個好,它可以在O(n)的復雜度內算出n個數的逆元
遞歸就是上面的寫法,加一個記憶性遞歸,就可以了
遞推這么寫
#include<cstdio> const int N = 200000 + 5; const int MOD = (int)1e9 + 7; int inv[N]; int init(){inv[1] = 1;for(int i = 2; i < N; i ++){inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;} } int main(){init(); }練習 :? ZOJ3609? hdu1576? poj1601(拓展歐幾里得)?
總結
以上是生活随笔為你收集整理的逆元~(乘法逆元及其应用)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 抽屉原理~
- 下一篇: zcmu1203(逆序对,归并排序)