数论入门知识
數論入門
提到數論,可能很多人都感到很頭疼,甚至很多時候遇到一些問題,看到成篇的證明都會感到恐懼,而且由于關于ACM方面的數論資料,網上資料都比較駁雜。有時候很容易出現知其然不知其所以然的情況。所以今天給大家介紹一些關于數論入門最基礎的知識和算法,內容會盡量從0基礎開始,所以內容會盡量詳細。不過其中部分證明還是需要一些高中數學基礎的。由于內容很多,我整理了一個目錄,按順序講今天的內容.
目錄
一.合數,質數,整除,互質,同余,取模等基礎概念。
二.歐幾里得算法
三.擴展歐幾里得
四.費馬小定理
五.歐拉函數
六.歐拉降冪
七.素數篩
八.快速冪
九.逆元的用處和幾種簡單求法
十.中國剩余定理
一.數論中的基礎概念和符號
1.基礎概念
合數:合數是指在大于1的整數中除了能被1和本身整除外,還能被其他數(0除外)整除的數
質(素)數:質(素)數是指在大于1的自然數中,除了1和它本身以外不再有其他因數的自然數。
整除:若整數b除以非零整數a,商為整數,且余數為零, 我們就說b能被a整除(或說a能整除b),b為被除數,a為除數,即a|b(“|”是整除符號),讀作“a整除b”或“b能被a整除”。a叫做b的約數(或因數),b叫做a的倍數。整除屬于除盡的一種特殊情況。
公約數:公約數,亦稱“公因數”。它是指能同時整除幾個整數的數 [1] 。如果一個整數同時是幾個整數的約數,稱這個整數為它們的“公約數”;公約數中最大的稱為最大公約數。對任意的若干個正整數,1總是它們的公因數。
互質:互質是公約數只有1的兩個整數,叫做互質整數
同余:數論中的重要概念。給定一個正整數m,如果兩個整數a和b滿足a-b能夠被m整除,即(a-b)/m得到一個整數,那么就稱整數a與b對模m同余,記作a≡b(mod m)。對模m同余是整數的一個等價關系
2.常見符號
MOD
mod,要與一般的%相區分
mod意為模意義下結果一定為正
%是一種運算,結果可以為負
同余符號(≡)
兩個整數a,b,如果a mod m = b mod m則稱a,b對于模m同余
記作a ≡ b ( mod m )
sigma(Σ )
求和符號
∑i=1ni\sum_{i=1}^{n}{i} i=1∑n?i
sigma的意思是i取值1(下界)到n(上界)后面的表達式的和,這個公式里的值是1 + 2 + 3 + ? ? ? + ( n ? 1 ) + n 1+2+3+···+(n-1)+n1+2+3+???+(n?1)+n
求積符號Π
∏i=1ni\prod_{i=1}^{n}{i} i=1∏n?i
求積符號,這個公式代表的就是n的階乘
同余≡
兩個整數a,b,若它們除以整數m所得的余數相等,則稱a,b對于模m同余
記作 a ≡ b (mod m) 讀作a同余于b模m,或讀作a與b關于模m同余。
比如 26 ≡ 14 (mod 12)
μ莫比烏斯函數
代表了莫比烏斯函數。
φ歐拉函數
在數論中代表歐拉函數
定義:小于n的正整數中與n互質的數的數目
|整除符號
整除符號:x|y,表示x整除y,即x是y的因數
gcd(x,y)
代表x和y的最大公約數,也可寫作(x,y)
lcm(x,y)
代表x和y的最小公倍數,也可寫作[X,Y];
二.歐幾里得算法
再說歐幾里得算法之前,我們不得不提到最大公約數( Greatest Common Divisor)一般縮寫為gcd,這歐幾里得算法,就是歐幾里得發明的用來求兩個數最大公約數的算法,也稱輾轉相除法。它的公式是
gcd(a,b)=gcd(b,amodb)gcd(a,b) = gcd(b,a mod b) gcd(a,b)=gcd(b,amodb)
它的證明也有很多種方法,這里我們只介紹一種基礎的證法
證明了這個公式,我們想要算出gcd(a,b)的結果也就很簡單了。這兩個數的大家都會不斷減小,我們只需要遞歸不斷縮小范圍低軌道b==0的時候就能算出a和b的最大公約數了,如果gcd(a,b)==1,說明a和b互質。
int gcd(int a, int b) {if (b == 0) return a;return gcd(b, a % b); } int gcd(int a , int b){return a%b==0? b:gcd(b,a%b); }它的時間復雜度是O(log n);其實很好理解,在遞歸求gcd的過程中,如果a>b程序會返回gcd(b,a)如果a>b。gcd(a,b)-=gcd(b,a mod b)取模后a的值至少會折半,所以這個過程只會發生logn次。而每次第一種情況后一定會轉移到第二種情況,第一種情況的次數不大于第二種情況,所以這個算法的總復雜度是O(log n)級別.
介紹完了最大公約數,不得不提的就是最小公倍數(Least Common Multiple)lcm了。
這里也有一個公式可以直接算,就是gcd(a,b)*lcm(a,b)=a * b
如何證明呢?
gcd(a,b)=t假設a=k1?t,b=k2?t易得gcd(k1,k2)=1lcm(a,b)=k1?k2?t可以發現gcd(a,b)?lcm(a,b)=a?bgcd(a,b)=t\\ 假設a =k1*t,b=k2*t\\ 易得gcd(k1,k2)=1\\ lcm(a,b)=k1*k2*t\\ 可以發現gcd(a,b)*lcm(a,b)=a*b\\ gcd(a,b)=t假設a=k1?t,b=k2?t易得gcd(k1,k2)=1lcm(a,b)=k1?k2?t可以發現gcd(a,b)?lcm(a,b)=a?b
需要注意的是,如果在代碼中使用,不要直接寫成(a*b)/gcd(a,b)而是先算a/gcd(a,b)在乘b,放置超出數據范圍。
三.擴展歐幾里得算法(exgcd)
裴蜀定理
在講擴展歐幾里得之前,我們先介紹一下一個定理,裴蜀定理
裴蜀定理,又稱貝祖定理(Bézout’s lemma)。是一個關于最大公約數的定理。
其內容是:設a,b是不全為零的整數,則存在整數x,y , 使得ax+by=gcd(a,b) .
證明就不詳細展開了,有興趣可以上網查一查,其實思想和輾轉相除法差別不大。
擴展歐幾里得定理
擴展歐幾里得算法實際上就是對于ax+by=gcd(a,b),一定有一組整數解x,y使其成立
對于這個式子的證明,可以采用數學歸納法進行實現,先證明當n= 1時命題成立。
假設n=m時命題成立,那么可以推導出在n=m+1時命題也成立。(m代表任意自然數)
然后命題得證。
關于它的代碼
inline int exgcd(int a, int b, int &x, int &y) {if(b == 0){x = 1, y = 0, return a;}int d = exgcd(b, a % b, x, y);int z = x;x = y, y = z - y * (a / b);return d; }通過他我們可以的到方程的一組特解。
知道了特解之后,我們可以通過他求出方程的所有解
x=x0+kbdy=y0?kadx=x_0+k\fracze8trgl8bvbq\\ y=y_0-k\frac{a}ze8trgl8bvbq\\ x=x0?+kdb?y=y0??kda?
有了這個式子。我們就可以求出任意方程ax + by = c , gcd ? ( a , b ) ∣ c的所有解為
x=cdx0+kbd.y=cdy0?kadx=\frac{c}ze8trgl8bvbqx_0+k\fracze8trgl8bvbq\\ .\\ y=\frac{c}ze8trgl8bvbqy_0-k\frac{a}ze8trgl8bvbq\\ x=dc?x0?+kdb?.y=dc?y0??kda?
關于擴展歐幾里得的證明和應用,這里我們先講這么多,在后面講逆元的過程中,還有用到的機會
四.費馬小定理
若p為質數,且gcd(a,p)=1,那么就有:
ap?1≡1(modp)a^{p-1} \equiv 1 \quad (mod \quad p) ap?1≡1(modp)
打公式怎么看格式都不好看,還是用紙來寫吧。
這是一種很巧妙的費馬小定理證法。但實際上正統的證明方法是引入了完全剩余系和縮剩余系。這些概念在代數系統中式很重要的,不過今天的目的知識給大家見簡單介紹數論的基礎,暫時不深入展開來講了。實際上歐拉定理和費馬小定理都是拉格朗日定理的一個推論。對于一個有限群,元素的階一定式群階數的因子。感興趣的同學可以在網上查一查相關證明。這里不多贅述
證明完了以后,我們來看看他有什么用
實際上,費馬小定理的應用場景還是很廣泛的,這里我們只從競賽的角度說幾個它的簡單應用。
1.它可以用來判斷大質數也就式Miller-Rabin質數判定
2.它可以用來進行費馬小定理降冪也就是
ak≡akmod(p?1)modpa^k\equiv a^{k\quad mod\quad(p-1)}mod \quad p ak≡akmod(p?1)modp
3.在后文求解逆元的過程中,我們也會用到費馬小定理
五.歐拉定理
我們直接來看歐拉定理
?a,m,若gcd(a,m)=1,則有:aφ(m)≡1(modm)\forall a,m, 若 gcd(a,m) = 1 ,則有: a^{\varphi(m)} \equiv 1 \quad (mod \quad m) ?a,m,若gcd(a,m)=1,則有:aφ(m)≡1(modm)
到了這里我們其實可以發現,費馬小定理就是歐拉定理的在m為素數下的一種特殊情況。通過這個定理在一些特殊情況下求解逆元很好用。并且有一些題目會考察對它的理解,在數論中歐拉定理的應用面非常廣泛。是一個非常重要的定理。不過涉及競賽的一般用的最多就是歐拉降冪公式。我們下面也會提到。這里的證明方法只是幫助大家理解他的本質。如果不理解,在競賽中,記住歐拉降冪公式和應用就夠了。不過理解這些公式的本質在所以寫考察思想的題目中還是有所幫助的。
六.歐拉降冪(擴展歐拉定理)
若a和m互質
aK≡akmodφ(m)(modm)a^K \equiv a^{k \ mod \ \varphi(m)} \quad (mod \quad m) aK≡ak?mod?φ(m)(modm)
若a和m不互質則有
?k>φ(m),有ak≡akmodφ(m)+φ(m)(modm)\forall k > \varphi(m),有 a^k \equiv a^{k \ mod \ \varphi(m) + \varphi(m)} \quad (mod \quad m) ?k>φ(m),有ak≡ak?mod?φ(m)+φ(m)(modm)
證明過于繁雜,會用實現降冪就行、
在一些計數的問題中,常常要求對結果取模,但是在計算非常龐大的次冪的時候,無法直接取模,可以先把底數對 p 取模,指數對 φ ( p )取模,再計算次冪,有效地降低時間復雜度。
這里給除一個例題幫助大家理解它的用法
計算abmodm,1<=a<=109,1<=b<=1020000000,1<=m<=108計算a^b mod\quad m,1<=a<=10^9,1<=b<=10^{20000000},1<=m<=10^8 計算abmodm,1<=a<=109,1<=b<=1020000000,1<=m<=108
七.線性篩
1.Miller-Rabin 素數測試
有時候我們想快速的知道一個數是不是素數,但是數太大這時候我們可以對其進行 Miller-Rabin 素數測試,可以大概率測出其是否為素數。利用了費馬小定理和二次探測定理,證明這里不再放出.這里給大家提供一個模板,實際上證明也不復雜,有興趣可以自己嘗試。內容過多不重要的的就不浪費時間了。
bool millerRabbin(int n){if(n<3)return n==2;int a=n-1,b=0;while(a%2==0)a/=2,++b; // test_time 為測試次數,建議設為不小于 8 的整數以保證正確率,但也不宜過大,否則會影響效率for(inti=1,j;i<=test_time;++i){int x=rand()%(n-2)+2,v=quickPow(x,a,n);if(v==1||v==n-1)continue;for(j=0;j<b;++j){v=(long long)v*v%n;if(v==n-1)break;}if(j>=b)return 0;}return 1; }埃氏篩
埃拉托斯特尼篩法,簡稱埃氏篩,是一種由希臘數學家埃拉托斯特尼所提出的一種簡單檢定素數的算法。要得到自然數n以內的全部素數,必須把不大于根號n的所有素數的倍數剔除,剩下的就是素數。它的實際運用流程其實也很好理解,我們先吧2-n之間的整數寫出來,2是最小的素數,把表中2的所有倍數刪去,然后現在表中最小的素數是3,由于他沒有被比他小的素數整除,他一定是現在最小的素數,我么你繼續刪去3的所有倍數,以此類推,直到篩到根號n為止。
算法時間復雜度是O(nlog(logn)).在數據范圍很大時基本可以看做線性復雜度,足以通過大部分題目。
for(int i=2;i<=N;i++){if(!vis[i]){for(int j=2*i;j<=N;j+=i){vis[j]=1;}}}這里是最基礎埃氏篩,一般其實夠用了,不過在某些情況下,我們還是需要對埃氏篩進行優化,這個優化從三個部分體現出來,我們分別來看他們是如何優化的。
1.首先是第一重優化,其實就是外層i的枚舉只需要到根號n即可,這樣足夠保證n以內的數被篩過。很好理解
2.第二重優化是一個小細節的優化,在第二重循環中,每次增加素數的1倍,但除了2以外素數都是奇數,那么奇數乘以偶數是偶數,偶數情況早已被2篩完,故每次增加2i倍.
3.第三重優化是在第二層循環中把j的起點從i*i開始,因為篩到i的時候已經能保證 i * i范圍內沒有合數了。
通過這三種常數優化,整個埃氏篩的復雜度幾乎和真正的線性篩相差無幾,甚至更快一點
for (int i = 0; i <= n; ++i) is_prime[i] = true; is_prime[0] = is_prime[1] = false; for(int i=2;i*i<=N;i++)//將i<=N換成i*i<=N {if(is_prime[i]){ int mul;//2篩過后偶數一定不是素數所以直接加2; i==2?mul=1:mul=2;for(int j=i*i;j<=N;j=j+i*mul)//將2*i換成i*iis_prime[j]=false;} }歐拉篩
埃氏篩已經很接近極限了,但是他還是可以繼續優化,在埃氏篩的過程中有的合數會被多次標記,浪費時間,如何解決這個問題呢?這就是是埃氏篩和歐拉篩的最大區別了
歐拉篩思想的核心是要保證的是每個合數只被這個合數最小的質因子篩除,而且只篩一次,沒有重復篩除,這里就需要用到這行神秘代碼if(i%prime[j]==0) break;。歐拉篩的難點就在于對if (i % prime[j] == 0)這步的理解,當i是prime[j]的整數倍時,記 m = i / prime[j],那么 i * prime[j+1] 就可以變為 (m * prime[j+1]) * prime[j],這說明 i * prime[j+1] 是 prime[j] 的整數倍,不需要現在篩出,因為在之后篩除過程中i * prime[j+1] 這個合數一定會被prime[j]篩除,prime[j]之后的所有素數同理,所以break跳出循環。
#include<bits/stdc++.h> using namespace std; const int maxn = 1e6+10; int cnt=0; //記錄已知素數數量 vector<int> primes; //存放素數的不定長數組 bool judge[maxn]; //篩除標記 int main() {int i,j;memset(judge,true,sizeof(judge));judge[0]=false;judge[1]=false;// 0和1都不是素數for(i=2;i<maxn;i++){if(judge[i]){primes.push_back(i);cnt++;//記錄素數個數}for(j=0;j<cnt && i*primes[j]<=maxn;j++){judge[i*primes[j]]=false; //篩除if(i%primes[j]==0) //關鍵代碼break;}}vector<int>::iterator it;for(it=primes.begin();it!=primes.end();it++)//遍歷printf("%d\n",*it);return 0; }他能做到真正的線性復雜度,因為他能保證每個數只被篩了一次。
歐拉篩篩歐拉函數
如果只是篩質數,大部分情況埃氏篩就夠了,那么歐拉篩還有什么用呢?他當然不知可以用來篩素數,記得在介紹歐拉定理時候就留下了一個問題,如何求歐拉函數。這里我們提供給大家兩個方法,大家可以根據題目情況自行選擇合適的方法。
1.公式法求歐拉函數
φ(a)=n?(1?1p1)?(1?1p2)?……?(1?1pn)φ(a)=n?(1-\frac{1}{p_1})?(1-\frac{1}{p_2})?……?(1-\frac{1}{p_n}) φ(a)=n?(1?p1?1?)?(1?p2?1?)?……?(1?pn?1?)
其中p1到pn是a用唯一分解定理分解出的所有質因數。
這里簡單提一下唯一分解定理,他也叫算數基本定理,任何一個大于1的自然數 N,如果N不為質數,那么N可以唯一分解成有限個質數的乘積
。最早的證明也是歐幾里得給出的,涉及到歐幾里得引理等內容,跟acm關系不大,主要是數論方向的一個重要定理,證明這里就不給出了,有興趣可以自己查查資料,或者私下來找我交流。
那么這個公式是如何得到的呢?這里需要用到一些積性函數的知識,如果要一起講的話時間肯定也是不夠的,所以暫時也不證了,后續講積性函數相關內容后很多東西都能聯系起來了。這里需要注意的是,在網上有很多資料用這個公式證明了歐拉函數是積性函數,需要注意的是,這種證法其實完全沒有意義。因為這個公式是用歐拉函數是積性函數推出來的,要搞清楚因果關系.
inline LL eular(LL x) {LL sum=x,y=x;for (LL i=2;i*i<=y;++i){if (x%i!=0) continue;sum=sum/i*(i-1); while (x%i==0) x/=i;}if (x>=2) sum=sum/x*(x-1);return sum; }2.歐拉篩求歐拉函數
這個過程會涉及積性函數的一些性質。如果要證明推式子,前置知識太多了,所以不給大家證了,直接作為結論給出,相關證明和知識網上也有很多,感興趣的同學可以自己看一看。這三個性質中的p就是歐拉篩中用來篩當前這個數的最小質因子。
如果i是素數,那么φ(i)=i?1如果imodp=0,那么φ(i?p)=p?φ(i)如果imodp≠0,i和p互質那么φ(i?p)=φ(i)?φ(p)=φ(i)?(p?1)如果i是素數,那么φ(i)=i?1\\ 如果i mod p=0,那么φ(i?p)=p?φ(i)\\ 如果i mod p≠0,i和p互質那么φ(i?p)=φ(i)?φ(p)=φ(i)*(p?1) 如果i是素數,那么φ(i)=i?1如果imodp=0,那么φ(i?p)=p?φ(i)如果imodp?=0,i和p互質那么φ(i?p)=φ(i)?φ(p)=φ(i)?(p?1)
inline void findphi(void) {phi[1]=1,prm[1]=0;for (LL i=2;i<=n;++i){if (!v[i]) {prm[++cnt]=i;phi[i]=i-1;//歐拉函數,質數的歐拉函數值為本身-1;}for (LL j=1;j<=cnt && i*prm[j]<=n;++j){v[i*prm[j]]=1;if (i%prm[j]==0) {phi[i*prm[j]]=phi[i]*prm[j]; break; }if (i%prm[j]!=0) phi[i*prm[j]]=phi[i]*(prm[j]-1); } }return; }八.快速冪
可以再O(logn)復雜度內解決問題,其實本質就是把要求的數的冪次看成二進制的數,然后然后分解成二進制的每一位。在進行相乘。
a11=a23?a21?a20a^{11}=a^{2^3}*a^{2^1}*a^{2^0} a11=a23?a21?a20
分解之后我們只需要知道a1,a2,a4,…。用logn次乘法就能算出an。
如果需要取模,就在每一步直接加上取模操作即可
long long pow(long long a,long long b,long long m) {a%=m;long long res=1;while(b>0) {if(b&1)res=res*a%m;a=a*a%m;b>>=1;}return res; }九.逆元的應用和幾種求法
1.逆元的定義
當 ax≡1(modb), x即為 a 在mod b 意義下的逆元。
逆元的數學符號是 inv ,a 在mod b 意義下的逆元記作 inv(a,b)。注意不要寫反了。
簡單來說逆元就是在mod某個數意義下的倒數例如5x≡1(mod3)x=2是滿足10=1(mod3)所以稱2是5在mod3意義下的逆元、這里需要特別注意只有a和p互質,a才有關于p的逆元。
2.逆元的應用
那么逆元有什么用呢?
(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 (錯)
在求余的過程中我們發現只有除法是不能分開運算的,而當a過大時,在計算除法過程中可能會造成比較大的精度損失,所以對于這種情況我們一般會把式子轉換成那么(a / b) % p = (a * inv(b) ) % p = (a % p * inv(b) % p) % p來進行計算。這樣就解決了除法不能分開計算的問題。
知道了這些那么我們繼續探討逆元是如何得到的。這里給大家提供4種方法,在不同的情況選選擇合適的方法來解決逆元。
3.逆元的4種求法
1.費馬小定理求逆元
上面我們也介紹了費馬小定理。
若p為質數,且gcd(a,p)=1,那么就有:
ap?1≡1(modp)a^{p-1} \equiv 1 \quad (mod \quad p) ap?1≡1(modp)
如果我們的模數p是質數。我們就可以用費馬小定理來求一個數的逆元。
因為費馬小定理規定了p一定為一個質數,所以a和p一定互質
那么雙方在modp的意義下同時除a可得
a^(p-2) ≡1/a (mod p)
也就是a^(p-2) ≡ inv(a) (mod p)
所以inv(a) = a^(p-2) (mod p)
代碼需要用快速冪實現,復雜度大概為o(logn)
適用情況
如果模數是質數的情況,這種方法是最快也是最好寫的。一般mod數是質數我們都會用這個。相信很多人應該也猜到如果模數是合數我們該如何推廣這個式子,其實就是把費馬小定理推廣到歐拉定理。不過這種方法需要O(n)線性篩先篩歐拉函數,在快速冪算逆元。復雜度不夠優秀而且寫起來很復雜。所以沒有必要。只是幫大家理解。并且需要注意的是,歐拉定理的前提條件也是a和p互素。剛好和有逆元的條件一樣。
aφ(p)≡1(modp)aφ(p)?1就是a的逆元了a^{\varphi(p)}\equiv 1(modp)\\ a^{\varphi(p)-1}就是a的逆元了 aφ(p)≡1(modp)aφ(p)?1就是a的逆元了
2.擴展歐幾里得算法求逆元
如果gcd(a,p)=1;
那么就有ax+py=1
雙方同時modp
就有ax≡1(modp)
因為py是p的倍數全部約掉了
此時x就是a的逆元
所以只需解出該情況下的擴展歐幾里得方程的解問題就解決了這里和上面介紹的擴展歐幾里得解方程方法一樣。
適用情況
只要存在逆元,就可以用exgcd來求解逆元.復雜度為O(logn),也是我們最常用的一種方法。適用于要求的逆元個數不多的情況。
3.遞推求逆元
我們來簡單推一下這個逆元的式子。
P是模數,i是我們要求逆元的數,那么實際上我們的實際目標就是求i?1在modp意義下的值首先p=k?i+r.其中k=p/i,r=pmodi也就是k?i+r≡0(modp)稍微轉化一下,同乘r?1?i?1可以得到式子k?r?1+i?1≡0(modp)那么就有i?1≡?k?r?1(modp)再把最開始的k和r的值帶進來。公式最后就變成了i?1≡?p/i?inv[pmodi];它的遞推起點就是1的逆元在任意情況下均為1;、、最后式子可以看成if(i=1)inv(i)≡1(modp)elseinv(i)≡?p/i?inv[pmodi](modp)P是模數,i是我們要求逆元的數,那么實際上我們的實際目標就是求i^{-1}在modp意義下的值\\ 首先p=k*i+r.其中k=p/i,r=p \quad mod\quad i\\ 也就是k*i+r\equiv0(modp)\\ 稍微轉化一下,同乘r^{-1}*i^{-1}\\ 可以得到式子k*r^{-1}+i^{-1}\equiv0(modp)\\ 那么就有i^{-1}\equiv-k*r^{-1}(modp)\\ 再把最開始的k和r的值帶進來。公式最后就變成了\\ i^{-1}\equiv-p/i*inv[pmodi];\\ 它的遞推起點就是1的逆元在任意情況下均為1;、、 最后式子可以看成\\ if(i=1)inv(i)\equiv1(modp)\\ else\quad inv(i)\equiv-p/i*inv[pmodi](modp) P是模數,i是我們要求逆元的數,那么實際上我們的實際目標就是求i?1在modp意義下的值首先p=k?i+r.其中k=p/i,r=pmodi也就是k?i+r≡0(modp)稍微轉化一下,同乘r?1?i?1可以得到式子k?r?1+i?1≡0(modp)那么就有i?1≡?k?r?1(modp)再把最開始的k和r的值帶進來。公式最后就變成了i?1≡?p/i?inv[pmodi];它的遞推起點就是1的逆元在任意情況下均為1;、、最后式子可以看成if(i=1)inv(i)≡1(modp)elseinv(i)≡?p/i?inv[pmodi](modp)
有了這個式子。我們就可以直接用遞推的方式O(n)求出1-n所有數的逆元。
LL inv[mod+5]; void getInv(LL mod) {inv[1]=1;for(int i=2;i<mod;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod; }適用情況
時間復雜度O(n)一般用于模數是素數且要多次調用逆元的情況,比如盧卡斯定理等。
遞歸求逆元
到這里其實也很容易發現了,如果我們只是單獨求某個數的逆元。完全可以用遞歸的方法去做,用的還是上面給出的公式.
LL inv(LL i) {if(i==1)return 1;return (mod-mod/i)*inv(mod%i)%mod; }適用情況
復雜度O(n1/3)至于它的復雜度證明比較復雜,但是實際情況復雜度可以看做o(logp)p是模數。為什么有兩種復雜度呢?這個算法本質上有兩種上界。并且大小不定。實際運算復雜度應該會取兩種復雜度中低的一種。根據實驗。大部分情況,這個算法的復雜度都是logp級別的,也就是說它的復雜度不會超過O(n1/3).那么他可以保證一定比擴展歐幾里得和費馬小定理優秀,至于上界為什么是O(n^1/3).有一篇論文給出了比較詳細的證明
https://cs.uwaterloo.ca/research/tr/1996/21/cs-96-21.pdfcs-96-21.dvi (uwaterloo.ca)
如果模數是質數直接用就行。代碼也短。
十.中國剩余定理(CRT)
孫子定理是中國古代求解一次同余式組的方法。是數論中一個重要定理。又稱中國余數定理。一元線性同余方程組問題最早可見于中國南北朝時期(公元5世紀)的數學著作《孫子算經》卷下第二十六題,叫做“物不知數”問題,原文如下:
有物不知其數,三三數之剩二,五五數之剩三,七七數之剩二。問物幾何?即,一個整數除以三余二,除以五余三,除以七余二,求這個整數。《孫子算經》中首次提到了同余方程組問題,以及以上具體問題的解法,因此在中文數學文獻中也會將中國剩余定理稱為孫子定理。
解決這個問題就要用到中國剩余定理。我們來看一下他的內容是什么
這里我們得到是一組構造出來的特殊接,方程的通解就是x+km如果要求最小非負解。只需要把x對m取模。保證x在0-m-1的范圍內即為最小正整數解。
這里的ti其實就是Mi的逆元
那么這個問題寫在代碼中就只需要求逆元即可。
const ll N = 5e5 + 7; int n, a[N], m[N]; ll exgcd(ll a, ll b, ll &x, ll &y) {if(b == 0){x = 1; y = 0;return a;}ll d = exgcd(b, a % b, x, y);ll z = x;x = y;y = z - (a / b) * x;return d; } int main() {ll M = 1;scanf("%d", &n);for(int i = 1; i <= n; ++ i) {scanf("%d%d", &m[i], &a[i]);M *= m[i];}ll res = 0;for(int i = 1; i <= n; ++ i) {ll Mi = M / m[i];ll ti, y;//exgcd求逆元:解同余方程:ax + my = 1;(ax ≡ 1 mod m)ll d = exgcd(Mi, m[i], ti, y);ti = (ti % m[i] + m[i]) % m[i];res += a[i] * ti * Mi;}printf("%lld\n", (res % M + M) % M);//可能為負數,所以需要處理一下return 0; }小結
今天講的內容其實只是數論知識的冰山一角,并且有些地方的證明還需要一些其他知識來輔助。希望大家在學習數論的過程中還是要注重理解證明。這些證明思想會是你學習過程中最大的收獲。本文盡可能用簡單的語言和基礎知識給一些數論基礎知識做了鋪墊。希望能幫到大家.有的地方latax掛掉了。換成圖片了
總結
- 上一篇: 下载开发证书步骤(自用备忘)
- 下一篇: JVM-垃圾收集器