逆元知識普及(掃盲篇) —— from Judge

 

watch out

本文是博主的 csdn 上搬過來的,格式有點崩,看不下去的能夠去 博主的 csdn 上看(上面 格式會好不少,而且有些公式也用 $\LaTeX$  update 上去了,可是博主也在 cnblogs 上更新了一下...lateX 都用上了,應該不至於不能看)html

 

 

最近有點頹廢啊,寫篇blog振做一下…(不過沒圖的數論blog真是不對我胃口)算法

emmm …首先介紹一下這是一篇關於數論中較爲重要 (主要可能常常要用到) 的一個知識分支—逆元
相信你聽到數論以後可能鼠標就想往網頁上的 X 鍵上移了,可是仍是勸你看一看, 總能有點收穫的吧
(反正我以爲本身講的相對網上其餘 blog 還算是蠻清楚的了)網站

解釋一下逆元:

首先同窗們小學的時候都學過除法吧(這個問題有點奇怪,滑稽)?
那麼你必定知道,「除以一個數等同於乘以這個數的倒數」對吧?
因此逆元是什麼東西呢? 首先這裏有個式子: $({a\over b}) \% p$,這個式子的答案怎麼求?
沒錯,暴力求是一種方法,可是當 $b$ 很是大的時候呢 ? 這個時候就要用到逆元了ui

這裏有一句口訣 「除以一個數再取模等同於乘以這個數的逆元再取模 」(自創) 
什麼意思? 設 $inv[b]$ 是 $b$ 的逆元, 那麼 $({a\over b}) \%p = (a*inv[b]) \%p$  
這下就你能大概理解逆元是個什麼東西(以及它大概是用來幹什麼的)了吧 ? spa

那麼逆元怎麼求 ? 首先這裏得糾正一點, 咱們不能直接說一個數的逆元是多少,
應該這麼說: 一個數 $x$ 在模 $p$ 的條件下的逆元是多少.(這點概念仍是蠻重要的)
其次,咱們不可貴知一個數的逆元有無窮多個,可是咱們只須要求得一個數的最小正整數逆元就好了.net

另外,一個數 $x$ 在模 $p$ 的條件下不必定有逆元, $x$ 關於 $p$ 的逆元存在 當且僅當 $x$ 和 $p$ 互質
這裏有一個推導: (設 $a$ 爲 $x$ 的逆元, $b$ 爲任意整數)code

 

 


$x * a ≡ 1 (mod\ p)$ = 將p連入式子=> $x*a = 1 -b*p$ => $x*a+b*p=1$htm

那麼咱們就獲得了一組新方程: $x*a+b*p=1$ 若$x$ 和 $p$不互質, 則 $x$和 $p$ 存在公約數 $d=gcd(x,p)>1$ blog

提取出$d$, 獲得: $d(x/d*a+p/d*b) = 1$ 移項獲得: $(x/d * a+ p/d *b) = 1/d$遞歸

易知 $x$ , $p$ 能整除 $d$, 因此括號內定爲整數, 又由於$d > 1$ ,等式右邊必爲真分數, 等式無解

證畢

 

 

 

那麼說了半天,逆元到底怎麼求呢 ? 彆着急,往下看—-
求逆元有好幾種方法, 可是在這裏咱們只討論兩種方法(一個偷懶用的 ,一個不容易翻車的),
不過在此以前咱們先來說講費馬小定理 (別懼怕,這玩意兒知道怎麼用就好,不會叫你推的)

這是費馬小定理的原始狀態: $a^p ≡ a (mod\ p)$ ,其中 $a$ , $p$ 互質 [感性理解一下 a 一直乘 a  會出現循環節,而循環節必然是 p-1 的因數,因此乘着乘着就變回來了]
而後, 原式 =移項 => $a^p - a ≡0 (mod\ p) $=提取 a => $a*(a^{p-1} - 1) ≡0 (mod\ p)$  
=兩邊同除以 a => $a^{p-1}-1 ≡0 (mod\ p)$ =再次移項 => $a^{p-1} ≡1 (mod\ p)$ 
因而 費馬小定理的結論就產生了(注意,這個結論很重要),求逆元時常常要用(主要偷懶求逆元時必用)


好了,如今咱們再來考慮逆元的求法

1、 蒙哥馬利快速冪模(偷懶求逆元大法):

首先聽到這麼高大上(主要是長)的名字,相信你的心裏必定是崩潰的,彷彿見到了高斯定理或者歐拉定理同樣(彆着急啊,擴歐等下才講)
但其實蒙哥馬利快速模這個算法並無那麼高大上,它的侷限性很大,只有在 $p$ 是質數的狀況下才可使用
首先咱們設 $inv(a)$ 是 $a$ 的逆元 那麼根據定義, $inv(a) * a ≡1 (mod\ p)$ 
再根據 費馬小定理 $a^{p-1} ≡1$ , 易得 $inv(a) * a ≡a^{p-1} (mod\ p)$ 
移項,得: $inv(a) ≡ a^{p-2}$ 
因而咱們獲得了快速冪模算法的一個前提條件: $inv(a) ≡ a^{p-2} (mod\ p)$

下面附上代碼(本身即興手打):
[這裏是位運算處理的方法,固然也有遞歸求的方法,那個代碼短一些,不過這個代碼我打的順手]

 

 //by Judge
 inline ll quick_pow(ll x,rint p){
    ll res=1;
    while(p){
        if(p&1) res=(res*x)%mod;
        x=(x*x)%mod, p>>=1;
    }
    return res;
 }
 inline inv(ll a){
     inv_a=quick_pow(a,mod-2);
     return inv_a;
 }

 

 

下面咱們不着急講第二種算法,先來說講這種快速模算法的應用:
求 $C(n,r) \% p$ 的值 , 其中 $p$ 是大質數(如$1e9+7$) ,那麼最後的答案是: 原式 = $n! * (r! * (n-r)!)^{p-2} \%p$(也就至關於 ${n!\over r! *(n-r)!} \% p$ ,就是加了逆元)

推導過程:

 

$C(n,r) (mod\ p) = {n! \over r! * (n-r)!} (mod\ p)$=多乘一個 1 => $ {n! \over r! * (n-r)!} * 1 (mod\ p)$ ----1式

∵ p是質數,再根據費馬小定理可得: $(r! * (n-r)!)^(p-1) ≡1 (mod\ p)$ ----2式

再將2式帶入1式中獲得: $ C(n,r) ≡ {n! \over r! * (n-r)!} * (r! *(n-r)!)^{p-1} (mod\ p) 指數相消獲得 $C(n,r) ≡n! * (r! * (n-r)!)^{p-2} (mod\ p)$

 

其實換元一下推導會簡潔得多...

令 tmp = r! * (n-r)! ,那麼有:

$C(n,r)%p = ( n! / tmp ) \%p = ( n! / tmp ) * 1 \%p $

= $( n! * tmp^{-1} ) * tmp^{p-1} \%p$

= $( n! * tmp^{p-2} ) \%p$ = 上述的式子

 

關於費馬小定理的清奇證實能夠看這裏

不過呢,博主好像有寫過費馬小定理的花式證實,在這裏

代碼實現:

 //by Judge
 #define ll long long
 const ll mod=; //1e9+7或者其餘題目給定的大質數
 inline ll quick_pow(ll x,rint p){
    ll res=1;
    while(p){
        if(p&1) res=(res*x)%mod;
        x=(x*x)%mod, p>>=1;
    }
    return res;
 }
 inline C_mod(rint n,rint m){
    ll a=1,b=1;
    for(rint i=2;i<=n;++i)
        a=(a*i)%mod;
    for(rint i=2;i<=m;++i)
        b=(b*i)%mod;
    for(rint i=2;i<=n-m;++i)
        b=(b*i)%mod;
    return (a*quick_pow(b,mod-2))%mod;
 }

 

 

而後您能夠試試用這個算法 A 掉乘法逆元的模板題(放心沒坑,只會T而已,要A的話得用線性推逆元):P3811 【模板】乘法逆元

 

 


而後是上文提到過的擴歐

2、擴展歐幾里得算法(通常咱們都講擴歐) :

這個東西講起來真是煩,一開始我還一直覺得擴歐就是在求逆元…其實擴歐是求同餘方程的解…
首先若是你不是萌新的話,那麼你應該看到過ex_gcd這樣(噁心)的字眼,
這玩意兒實際上是擴歐…(ex 表示擴展, gcd 最大公約數是能夠用歐拉求的嘛)

不廢話了,進入正題, 首先擴歐公式以下: $ax+by = (a,b)$   [注意這裏是等於]
[$(a,b)$ 表明 $gcd(a,b)$ ,$ a$ 和 $b$ 是已知數, 要求的是 $x$ 和 $y$ 的一組解]
那麼很容易能夠知道, 當 a 和 b 互質的狀況下,原來的等式就變成了: $ax+by = 1$  
而後咱們怎麼用這玩意兒求逆元呢? 咱們先考慮求逆元的式子: $a * inv[a] ≡1 (mod\ p)$ 
其中$inv[a]$ 同上表示 $a$ 的逆元, 如今咱們令 $x = inv[a] , b = p$;
那麼同餘的式子就成了: $ax ≡1 (mod b)$ , 而後咱們把 $b$ 加入式子中,就成了這樣:
$ax+by = 1$ (這裏我就不慢慢推了,看博客也是要動動腦子的嘛 )其中 $y$ 和 $x$ 同樣,也是未知數
emmmm仍是講講, $ax ≡1 (mod b)$ => $ax = by+1$ (而後就把同餘去掉了,變成了等式)
移項 => $ax+by = 1$ (因爲 $y$ 是不肯定的,並且咱們求的主要是 $x$ 因此 $y$ 的正負性沒什麼影響)

這樣你應該就能看懂了, 擴歐公式中求解的 $x$ 就是 $a$ 的逆元(一切好像都很是的顯然了)
ok, 我仍是沒有講 擴歐中的 $x$ 和 $y$ 怎麼解 (這個纔是真正麻煩的地方,解釋起來很麻煩) 對吧?
哎, 式子先列出來再說, 我再斟酌斟酌…

$ax+by =(a,b) =1 , bX+(a\%b)Y =(b,a\%b) =1$ (注意大小寫,而且顯然這裏要知足 $a$  $b$ 互質的條件)
=> ………… => $x=Y , y=X-\lfloor {a\over  b}\rfloor*Y$ (其中 $X$ 和 $Y$ 已知)
好了,本身推導吧! (試圖想混過去的樣子) 咳咳… 首先 $ax+by = bX+(a\%b)Y$ 對吧? (兩式合併)
而後咱們把 $a\%b$ 轉化一下,變成: $ax+by = bX+(a-b*\lfloor{ a\over b} \rfloor ) Y$ (其實就是用了模的定義式)
=> $ax+by = bX+aY-b* \lfloor{ a\over b} \rfloor Y$ =提取公因式 a,b => $a(x-Y) = b(X- \lfloor {a\over b}\rfloor Y-y) $

那麼很顯然, 咱們令 $x = Y$ , 令 $y = X-\lfloor{ a\over b} \rfloor Y$ 就能輕鬆的構造出一組 x 和 y 的解了

然而如今又出現了一個問題: X 和 Y 怎麼求? 其實咱們同上的方法求出 X 和 Y 就好了:
$bX+(a\%b)Y=(a\%b)X’ + (b\%(a\%b))Y’$ 若是你還不能理解, 那麼咱們令 $p = b$, $q = a\%b$ ,
那麼原式 => $pX+qY=qX’+(p%q)Y’$ 這不就和以前的式子差很少了嗎?
如今你應該很容易就能看出來這是在用遞歸的方法求解原式的解

可是遞歸老是有有邊界的,不能永遠遞歸下去對吧(很是顯然)
那麼這個遞歸式子的邊界是什麼呢? emmmm 提示一下, 這個邊界和 gcd 的邊界有點關係
對, 邊界就是 b 等於 0 的時候, 咱們不能再遞歸下去了,那麼原式就成了:
$1x+0y=1$ [a此時爲 $(a,b)$ ,b爲 0 ] 這應該很好解吧? 咱們令 $x=1$,$y=0$ 就好了(y=0是爲了…方便嘛)

而後遞歸求解同上,下面附上代碼(也是即興手打的)
[下面常規版的,偷懶版和這個其實本質上沒有區別,只是簡化了一下代碼,那麼這裏就不附上了,有興趣的同窗能夠自行百度]

//by Judge
#define ll long long
const int mod=; //同上
 void ex_gcd(ll a,ll b,ll &x,ll &y){
    if(!b){ x=1,y=0; return ; }
    ex_gcd(b,a%b,x,y);
    ll t=x; x=y,y=t-(a/b)*y;
 }
 //固然你也能夠這麼寫,更能體現公式: 
 //  ll X=x,Y=y;
 //  x=Y,y=X-(a/b)*Y;
 inline ll inv(ll a){
    ll inv_a,y;
    ex_gcd(a,mod,inv_a,y);
    return (inv_a%mod+mod)%mod;
 }

 

 

 

另外這裏補充一下線性推逆元的方法 ($inv[i]$ 表示 $i$ 的逆元 , $fac[i]$ 表示 $i$ 的階乘 , $p$爲模數)

3、線性推逆元

1.求 1! ~ n! 的逆元:(由於我覺的階乘的逆元反倒比較好推因此就先講)

公式: $inv[i] ≡inv[i+1]*(i+1) (mod\ p)$

推導過程:

$fac[i] * inv[i] ≡1 (mod\ p)$

$fac[i+1] * inv[i+1] ≡1 (mod\ p) => fac[i]* (i+1) * inv[i+1] ≡1(mod\ p)$

由 同餘的除法原理可得 : $inv[i] ≡inv[i+1] * (i+1)$

推導完畢

 

inline void get_finv(){
    fac[1]=finv[0]=1;
    for(int i=2;i<=n;++i;++i)
        fac[i]=fac[i-1]*i%mod;
    finv[n]=quick_pow(fac[n],mod-2);
    for(int i=n-1;i;--i)
        finv[i]=finv[i+1]*(i+1)%mod;
}

 

 
 

2.求 1 ~ n 的逆元(這個還稍微麻煩些)

公式:$inv[i] ≡inv[p%i] * (- p/i) (mod\ p)$

推導過程:

令 $s = p/i , t = p\%i$ , 則有:

$s*i + t = p$ (顯然)

而後 $s*i + t ≡ 0 (mod\ p)$

移項得 $t ≡ -s*i (mod\ p)$

同除以 $t * i 得 t / (t*i) ≡ -s*i / (t*i) (mod\ p)$

將除法轉化爲乘法 => $inv[i] ≡ -s * inv[t] (mod\ p)$

因而將 $s=p/i , t=p\%i$ 帶入就能夠獲得:

$$inv[i] ≡ inv[p\%i] * (-p/i) (mod\ p)$$

推導完畢

代碼:

 

inline void get_inv(){
    inv[0]=inv[1]=1;
    for(int i=2;i<=n;++i)
        inv[i]=inv[mod%i]*(mod-mod/i)%mod;
}

 

 

而後你還能夠用線性逆元求階乘逆元了

 

// 這裏是一種用線性逆元解階乘逆元的方法 
inline void get_finv2(){
    finv[0]=finv[1]=1;
    for(int i=2;i<=n;++i)
        finv[i]=finv[mod%i]*(mod-mod/i)%mod;
    for(int i=2;i<=n;++i)
        finv[i]=finv[i]*finv[i-1]%mod;
}

 

 

若是你看完這些不過癮,給你幾個網站用來生啃代碼:
四法求逆元
逆元的幾種求法

最後的最後,逆元的這些東西…隨便學學就好,到時候用獲得(也可能用不到)
因而Judge的課堂又這樣水過去了一節…
歡迎下次光臨! _(:з」∠)_

 

 

(求點贊,求推薦,求收藏! ღ( ´・ᴗ・` )比心 )

相關文章
相關標籤/搜索