2021 SDSC D1 基礎數論
Part 1 初月(Easy Mode)
質數
暴力
從 \(2\) 列舉到 \(⌊\sqrt n⌋\) 試試是否可以整除就可以啦。
顯然時間複雜度是 \(O(\sqrt n)\) 的。
埃氏篩
剛才的試除法讓我們知道,找到一個數的因數是很難的。
但是找到一個數的倍數就很簡單了。
因此我們可以從 \([2,n]\) 中依次列舉,每個數的倍數必然不是質數
複雜度?
\(F(n)=\frac{n}{2}+\frac{n}{3}+⋯+\frac{n}{n}=O(n \log n)\)。
實際上底數是 \(e\),因此調和級數的常數很小,\(1s\) 跑得過。
由算術基本定理可知,只需要列舉質數的倍數即可。
列舉 \(p\) 的倍數時,只需從 \(p^2\) 開始列舉。
時間複雜度 \(O(n \log \log n)\)。
線性篩
剛才的做法已經很優秀了,可是為什麼還不是線性呢?
原因仍然在於,一個數字可能被篩掉多次。
例如 \(n=15\),那麼\(12\) 這個數會在 \(p=2\) 和 \(p=3\) 時各被篩一次。
想要做到線性,就必須讓一個數只被一個質數篩掉,我們選擇這個數為它最小的質因數。
首先從 \(2\)到 \(n\) 列舉自然數 \(q\)(不一定是質數),再從小到大列舉比 \(q\) 小的質數 \(p\),篩掉 \(pq\);如果 \(q\) 是 \(p\) 的倍數,就跳出內層迴圈。
void primes(int n) { memset(v, 0, sizeof(v)); //清空標記陣列 m = 0; //質數個數 for (int i = 2; i <= n; i++) { if (!v[i]) //未被標記,i為質數 v[i] = i, prime[++m] = i; //記錄 for (int j = 1; j <= m; j++) { if (prime[j] > v[i] || prime[j] > n / i) break; //i有更小的質因子,或者超出n的範圍 v[i * prime[j]] = prime[j]; //prime[j]為合數 i*prime[j]的最小質因子 } } }
根據“高斯素數定理”,如果需要粗略的估計 \([2,n]\) 內的素數個數,可以直接硬點它為 \(\frac{n}{\ln n}\)。
GCD & LCM
GCD,Greatest Common Divisor 的縮寫,意為最大公約數
LCM,Least Common Multiple 的縮寫,意為最小公倍數
\(\gcd(a,b)=\gcd(a−b,b)= \gcd(b,a\text{ mod } b)\)
注意到每次取模至少減半,因此複雜度為 \(O(\log n)\)
而求 LCM 只需求出 GCD ,因為:
\(\gcd(a,b) \times \text{lcm}(a,b)=a \times b\)。
進位制轉換
將 \(n\) 位 \(a\) 進位制數轉化為 \(b\) 進位制數,最終為 \(m\) 位。
將給定的 \(a\) 進位制數從高位向低位掃描,每次將當前結果乘以 \(a\),並加上當前位的係數,再從低位向高位進位即可。
複雜度 \(O(nm)\),用多項式科技可以優化,但大多數情況下沒必要。
瓶頸在於 \(n\) 次乘 \(a\) 和進位,每次都是 \(O(m)\) 的。
如果 \(a, b\) 比較小,可以通過壓位的方式進一步優化。
即,先將原數轉化為 \(a^p\) 進位制,再轉化為 \(b^q\) 進位制,可以將複雜度降為 \(O(\frac{nm}{pq})\),為了計算方便,以 \(a^p,b^q\) 不超過 \(2×10^9\) 為妙。
Part 2 三日月(Normal Mode)
同餘
若對於給定的正整數 \(m\),有正整數 \(a,b\),滿足 \(a=km+b,k∈Z\),則稱 \(a,b\) 模 \(m\) 同餘,用 \(a≡b(\text{mod}m)\) 表示。
模 \(m\) 意義下,整數集只剩下了 \([0,m)\) 的 \(m\) 個整數,其他整數都應該加上或減去若干個 \(m\) 來調整到這個區間內。
注意到負整數 \(x\) 對應的數是 \(km+x\),而不是 \(km−x\)。
順便提一句,\(a\text{mod}b=a−⌊\frac ab⌋ \times b\),這一點後面還會提到。
不難證明,模意義下的加法、減法、乘法與一般意義下相同,各運算規律依然滿足。
但是很可惜,除法與它們不同。
考慮一般意義下的除法,\(a÷b\) 等價於 \(a \cdot b^{-1}\),而 \(b^{−1}\) 滿足 \(b\cdot b^{−1}=1\) 這一性質,從而進行乘法的逆運算。
換句話說,假如我們能對任意 \(b\) 找到 \(b^{−1}\),使得\(b\cdot b^{−1}≡1(\text{mod}m)\),不就能實現模 \(m\) 意義下的除法了嗎?
逆元
因此,我們需要引入逆元的概念。
大多數情況下,當我們需要在模意義下做除法時,模數都是質數。
此時 \((0,m)\) 中每個整數都有唯一的逆元。
費馬小定理
若整數 \(a∈(0,m)\) 且 \(m\) 為質數,則 \(a^{m−1}≡1(\text{mod}m)\)。
有了它,我們就能求出 \(a\) 的逆元了:
\(a\cdot a^{m−2}=a^{m−1}≡1(\text{mod}m)\),所以 \(a^{−1}≡a^{m−2}(\text{mod}m)\)。
絕大多數情況下逆元都是這樣求的。
擴充套件歐幾里得演算法 & 裴蜀定理
設 \(x=a^{−1}\),那麼有 \(a\cdot x≡1(\text{mod}m)\)。
將它寫成一般形式,得到 \(x,y\) 的不定方程 \(ax−my=1\)。
如果能得到一組整數解 \((x_0,y_0)\),我們就可以得到它的通解:
\(x=x_0+km,y=y_0+ka,k∈Z\),選擇合適的 \(k\) 即可得到 \(x\)。
現在問題在於,方程是否有解,以及如何找到一組特解。
必要性很好證明,而下面要講的擴充套件歐幾里得演算法則通過構造證明了充分性。
設 \(d=\gcd(a,b)\),擴充套件歐幾里得演算法可以給出不定方程 \(ax+by=d\) 的一組整數特解 \((x_0,y_0)\)。
類比剛才的情況,通解有 \(x=x_0+k\cdot \frac bd,y=y_0−k\cdot \frac ad,k∈Z\)。
對於 \(ax+by=c,d|c\) 的情況,可以先解 \(ax+by=d\),再將 \(x,y\) 都乘以 \(\frac cd\)。
考慮之前歐幾里得演算法求 \(\gcd\) 的過程,我們從 \(\gcd(a,b)\)遞迴到 \(\gcd(b,a\text{mod}b)\)。
設 \(a^′=b,b^′=a\text{mod}b\),那麼如果我們有了不定方程 \(a^′x+b^′y=d\) 的一組整數解 \((x^′,y^′)\),能否推出 \(ax+by=d\) 的一組整數解 \((x,y)\) 呢?
\(b^′=a\text{mod}b=a−⌊a/b⌋ \cdot b\),代回 \(a^′x^′+b^′y^′=d\) 得到 \(bx^′+(a−⌊a/b⌋\cdot b)y^′=d\),整理得$ ay′+b(x′−⌊a/b⌋\cdot y^′)=d$。
也就是說,令 \(x=y^′,y=x^′−⌊a/b⌋\cdot y^′\) 即可完成遞迴的回推
裴蜀定理:對於給定的正整數 \(a,b\),設 \(d=\gcd(a,b)\),則關於 \(x,y\) 的不定方程 \(ax+by=c\) 存在整數解,當且僅當 \(d|c\)。
尤拉函式
尤拉函式:定義域為正整數的函式 \(φ(x)\),表示 \([1,x]\) 中與 \(x\) 互質的正整數個數。
設 \(x=∏p_i^{e_i}\),則 \(φ(x)=x\cdot ∏\frac{p_i−1}{p_i}\),證明可以考慮每次劃掉 \(x\) 所有質因子的倍數,剩下的就是與 \(x\) 互質的。
尤拉函式可以線性篩,只需判斷當前質因子是否出現過,以選擇乘以 \(p\) 還是 \(p−1\)。
尤拉定理
那麼為什麼要講尤拉函式呢?就是為了引出尤拉定理!
設 \(a,m\) 互質,則 \(a^{φ(m)}≡1(\text{mod}m)\)。
將 \([1,m]\) 中與 \(m\) 互質的數列出來,設為 \(b_1,b_2,⋯,b_{φ(m)}\)。
設 \(c_i=a\cdot b_i\text{mod }m\),可以證明 \(c_i\) 兩兩模 \(m\) 不同餘,且任意 \(c_i\) 與 \(m\) 互質。
那麼 \(c\) 其實是 \(b\) 重排的結果,因為與 \(m\) 互質的數只有 \(φ(m)\) 個。
因此 \(∏ b_i≡∏ c_i≡a^{φ(m)}\cdot ∏ b_i(\text{mod}m)\),得證。
前面提到的費馬小定理其實是尤拉定理的子定理。
擴充套件尤拉定理
剛才的尤拉定理只適用於 \(a,m\) 互質的情況
對於不互質的情況,此處不證明地給出擴充套件尤拉定理:
若 \(c<φ(m),則a^c≡a^c(\text{mod}m)\)
若 \(c≥φ(m),則 a^c≡a^{[cmodφ(m)]+φ(m)}(\text{mod}m)\)
證明比較複雜,有興趣的同學可以自行查閱或瀏覽這篇部落格。