今天我們來探討逆元在ACM-ICPC競賽中的應用,逆元是一個很重要的概念,必須學會使用它。
對於正整數和
,如果有
,那么把這個同余方程中
的最小正整數解叫做
模
的逆元。
逆元一般用擴展歐幾里得算法來求得,如果為素數,那么還可以根據費馬小定理得到逆元為
。
推導過程如下
求現在來看一個逆元最常見問題,求如下表達式的值(已知)
當然這個經典的問題有很多方法,最常見的就是擴展歐幾里得,如果是素數,還可以用費馬小定理。
但是你會發現費馬小定理和擴展歐幾里得算法求逆元是有局限性的,它們都會要求與
互素。實際上我們還有一
種通用的求逆元方法,適合所有情況。公式如下
現在我們來證明它,已知,證明步驟如下
接下來來實戰一下,看幾個關於逆元的題目。
題目:http://poj.org/problem?id=1845
題意:給定兩個正整數和
,求
的所有因子和對9901取余后的值。
分析:很容易知道,先把分解得到
,那么得到
,那么
的所有因子和的表達式如下
所以我們有兩種做法。第一種做法是二分求等比數列之和。
第二種方法就是用等比數列求和公式,但是要用逆元。用如下公式即可
因為可能會很大,超過int范圍,所以在快速冪時要二分乘法。
其實有些題需要用到模
的所有逆元,這里
為奇質數。那么如果用快速冪求時間復雜度為
,
如果對於一個1000000級別的素數,這樣做的時間復雜度是很高了。實際上有
的算法,有一個遞推式如下
它的推導過程如下,設,那么
對上式兩邊同時除,進一步得到
再把和
替換掉,最終得到
初始化,這樣就可以通過遞推法求出
模奇素數
的所有逆元了。
另外模
的所有逆元值對應
中所有的數,比如
,那么
對應的逆元是
。
題目:http://www.lydsy.com/JudgeOnline/problem.php?id=2186
題意:求中
互質的數的個數,其中
。
分析:因為,所以
,我們很容易知道如下結論
對於兩個正整數和
,如果
是
的倍數,那么
中與
互素的數的個數為
本結論是很好證明的,因為中與
互素的個數為
,又知道
,所以
結論成立。那么對於本題,答案就是
其中為小於等於
的所有素數,先篩選出來即可。由於最終答案對一個質數取模,所以要用逆元,這里
求逆元就有技巧了,用剛剛介紹的遞推法預處理,否則會TLE的。
代碼:
- #include <iostream>
- #include <string.h>
- #include <stdio.h>
- #include <bitset>
- using namespace std;
- typedef long long LL;
- const int N = 10000005;
- bitset<N> prime;
- void isprime()
- {
- prime.set();
- for(int i=2; i<N; i++)
- {
- if(prime[i])
- {
- for(int j=i+i; j<N; j+=i)
- prime[j] = false;
- }
- }
- }
- LL ans1[N],ans2[N];
- LL inv[N];
- int main()
- {
- isprime();
- int MOD,m,n,T;
- scanf("%d%d",&T,&MOD);
- ans1[0] = 1;
- for(int i=1; i<N; i++)
- ans1[i] = ans1[i-1] * i % MOD;
- inv[1] = 1;
- for(int i=2;i<N;i++)
- {
- if(i >= MOD) break;
- inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
- }
- ans2[1] = 1;
- for(int i=2; i<N; i++)
- {
- if(prime[i])
- {
- ans2[i] = ans2[i-1] * (i - 1) % MOD;
- ans2[i] = ans2[i] * inv[i % MOD] % MOD;
- }
- else
- {
- ans2[i] = ans2[i-1];
- }
- }
- while(T--)
- {
- scanf("%d%d",&n,&m);
- LL ans = ans1[n] * ans2[m] % MOD;
- printf("%lld\n",ans);
- }
- return 0;
- }
接下來還有一個關於逆元的有意思的題目,描述如下
證明:由
其中
所以只需要證明,而我們知道
模
的逆元對應全部
中的所有數,既是單射也是滿射。
所以進一步得到
證明完畢!