數論筆記-整除

空白菌發表於2023-01-14

整除

整除的定義與基本性質

定義\(a,b \in \Z\) ,若 \(b\) 除以 \(a\) 餘數為 \(0\) ,則稱 \(a\) 整除 \(b\) ,記為 \(a \mid b\)

\(a,b,c \in \Z\)

性質1 \(a \mid b \text{ 且 } b \mid c \Rightarrow a \mid c\)

性質2 \(a \mid b \Rightarrow a \mid kb\) ,其中 \(k \in \Z\)

性質3 \(a\mid b \iff ka \mid kb\) ,其中 \(k \in \Z^*\)

性質4 \(a \mid b \text{ 且 } a \mid c \Rightarrow a \mid kb + mc\) ,其中 \(k,m\in \Z\)

性質5 \(a \mid b \text{ 且 } b \mid a \Rightarrow a = \pm b\)

性質6 \(a\mid b \Rightarrow |a| \leq |b|\) ,其中 \(b \neq 0\)

性質7\(c=ka + b\) ,則 \(a\mid b \iff a\mid c\) ,其中 \(a \neq 0 , k \in \Z\)

性質8 \(a = kb \pm c \Rightarrow a,b \text{ 的公因數與 } b,c \text{ 的公因數相同}\)

性質9 \(a \mid bc \text{ 且 } \gcd (a,c) = 1 \Rightarrow a \mid b\)

性質8證明:

\(a,b\) 任意公因數為 \(d\) ,那麼 \(a = kb \pm c \Rightarrow c = \pm(a-kb)\) ,因此 \(c\) 也有因數 \(d\) ,所以 \(b,c\) 有公因數 \(d\)

\(b,c\) 任意公因數為 \(d\) , 那麼顯然 \(a\) 也有因數 \(d\) ,所以 \(a,b\) 具有公因數 \(d\)

綜上 \(a,b\) 的公因數與 \(b,c\) 的公因數相同。

素數

素數的定義與基本性質

定義 素數(質數)是隻有 \(1\) 和它本身兩個因數的數,否則為合數。

約定 \(1\) 既不是素數,也不是合數。

偶素數 \(2\) 是唯一的偶素數。

素數定理

\(\pi(n)\)\([1,n]\) 中素數的個數。

素數分佈存在漸進, \(\pi(n) \sim \dfrac{n}{\ln n},n \rightarrow \infty\)

由素數定理,可以得到如下定理:

定理1 對於 \(n \in \N^+\) ,有 \(\pi(n) \approx \dfrac{n}{\ln n}\)

定理2(伯特蘭-切比雪夫定理) 對於任意整數 \(n\geq 4\) ,存在質數 \(p\) 滿足 \(n < p < 2n-2\)

推論1(定理2的推論) 對於任意整數 \(n\geq 2\) ,存在質數 \(p\) 滿足 \(n < p < 2n\)

素數判定

試除法

一個數 \(n\) 若是合數,則一定在 \([1,\sqrt n]\) 中存在一個質數整除 \(n\)

證明:

\(d\)\(n\) 的一個質因子,則 \(\dfrac{n}{d}\) 也能整除 \(n\) 。假設 \(d \leq \dfrac{n}{d}\) ,則 \(d \leq \sqrt{n}\)

時間複雜度 \(O(\sqrt n)\)

空間複雜度 \(O(1)\)

bool isPrime(int n) {
    if (n == 2) return 1;
    if (n == 1) return 0;
    for (int i = 2;i * i <= n;i++) if (!(n % i)) return 0;
    return 1;
}

\(kn+i\)

試除法的升級版,常數更小。

一個數 \(n\) 若是合數,則一定存在 \([1,\sqrt n]\) 的質因子。因此,在試除法的基礎上,列舉因子時只考慮可能成為質因子的因子。

例如 \(k = 30\) 時,只有 \(i = 1,7,11,13,17,19,23,29\) 時的數才有可能成為質因子,其他情況都與 \(30\) 有非 \(1\) 的公因子一定不是素數。如此,演演算法計算量變為原來的 \(\dfrac{4}{15}\)

\(k = 30\) 時,時間複雜度 \(O(\frac{4}{15} \sqrt n)\)

空間複雜度 \(O(1)\)

bool isPrime(int n) {
    if (n == 2 || n == 3 || n == 5) return 1;
    if (n == 1 || !(n % 2) || !(n % 3) || !(n % 5)) return 0;
    int a[8] = { 4,2,4,2,4,6,2,6 }, p = 0;
    for (int i = 7;i * i <= n;i += a[p++], p %= 8) if (!(n % i)) return 0;
    return 1;
}

預處理法

尤拉篩 \(O(n)\) 預處理所有素數後,試除法可以直接列舉質因子。

根據素數定理,素數佔比約為 \(\dfrac{1}{\ln n}\) ,因此複雜度變為原來的 \(\dfrac{1}{\ln n}\)

時間複雜度 \(O(\frac{\sqrt n}{\ln n})\)

空間複雜度 \(O(n)\)

Miller-Rabin素性測試

Miller-Rabin素性測試通常用於判定大數(遠超 \(2^{64}\) 範圍)是否為素數,其是費馬素性測試的改進版。

眾所周知,費馬小定理:

\(a\) 不是 \(p\) 的倍數,若 \(p\) 為素數,則 \(a^{p-1} \equiv 1 \pmod p\)

但其逆命題:

\(a\) 不是 \(p\) 的倍數,若 \(a^{p-1} \equiv 1 \pmod p\) ,則 \(p\) 為素數。

並不總是成立。例如,\(2^{341-1} \equiv 1 \pmod{341},3^{1105-1} \equiv 1 \pmod{1105}\) ,這些數稱為費馬偽素數

但事實上,費馬偽素數的機率並不是很大。我們可以對一個數 \(p\)\(k\)\(a\) 測試,若存在 \(a^{p-1} \not\equiv 1 \pmod p\) ,則 \(p\) 一定是合數;否則,\(p\) 很大機率是素數。這個過程稱為費馬素性測試,時間複雜度為 \(O(k \log n)\) 。可惜的是,到 long long 範圍,費馬素性測試的準確性就已經開始不高了。因此,在此基礎上有了改進的演演算法Miller-Rabin素性測試

Miller-Rabin素性測試在費馬素性測試的基礎上增加了二次探測定理的使用。二次探測定理:

\(p\) 為奇素數,則 \(x^2 \equiv 1 \pmod p\) 的解為 $x \equiv \pm 1 \pmod{p} $ 。特別地,在 \([0,p-1]\) 的解為 \(x = 1\)\(x = p-1\)

因為只能探測奇素數,所以偶數要開始前判掉。

我們先將 \(p-1\) 拆分為 \(2^rm\)\(m\) 為奇數,\(r \geq 1\) ),隨後考慮對 \(x = a^m,a^{2m},\cdots,a^{2^{r-1}m}\) 進行二次探測。根據費馬素性測試,這個 \(x\) 序列探測到最後的結果應是 \(1\) 。我們對出現 \(1\) 的情況分類討論:

  1. 如果一開始 \(a^{m} \equiv \pm 1 \pmod p\) 成立,後續探測全是 \(x^2 \equiv 1 \pmod p\) 就不需要判斷了。
  2. 若不成立,則一定要在 \(r-1\) 次探測內先得到 \(x^2 \equiv - 1 \pmod p\) ,否則最後一定出現結果不為 \(1\) 或者出現 \(x^2 \equiv 1 \pmod p\)\(x \not \equiv \pm 1 \pmod p\) 的情況,則 \(p\) 為合數。

判定大數 \(n\) 是否為素數的具體步驟:

  1. 特判 \(n=1,2\) 以及其他所有偶數。
  2. \(n-1\) 拆分成 \(2^rm\) ,若 \(a^{m} \equiv \pm 1 \pmod{n}\) ,則後續全是 \(1\) 不需要判斷,否則下一步。
  3. 列舉 \(k\) 個(通常為 \(8\)\(10\) 個) \(a \in [1,n-1]\) 保證不是 \(n\) 的倍數,對 \(x = a^m,a^{2m},\cdots,a^{2^{r-2}m}\) 進行共 \(r-1\) 次二次探測,是否經過 \(-1\)
  4. \(k\) 次測試都透過,則 \(n\) 大機率為素數;某次沒透過就一定是合數。

時間複雜度 \(O(k \log n)\)

空間複雜度 \(O(1)\)

namespace Miller_Rabin {
    template<class T>
    T randint(T l, T r) {
        static mt19937 eng(time(0));
        uniform_int_distribution<T> dis(l, r);
        return dis(eng);
    }
    ll qpow(ll a, ll k, ll P) {
        ll ans = 1;
        while (k) {
            if (k & 1) ans = (__int128_t)ans * a % P;
            k >>= 1;
            a = (__int128_t)a * a % P;
        }
        return ans;
    }
    bool isPrime(ll n, int k = 10) {//8-10次
        if (n == 2) return 1;
        if (n == 1 || !(n & 1)) return 0;
        int r = __builtin_ctzll(n - 1);
        ll m = n - 1 >> r;
        while (k--) {
            ll x = qpow(randint(1LL, n - 1), m, n);
            if (x == 1 || x == n - 1) continue;//直接滿足,否則r-1次內必須有n-1
            for (int i = 1;i <= r - 1 && x != 1 && x != n - 1;i++) x = (__int128_t)x * x % n;//二次探測
            if (x != n - 1) return 0;//未經過n-1
        }
        return 1;
    }
}

素數篩法

埃氏篩

素數的倍數一定是合數,合數的倍數一定被某個質因子的倍數篩掉了,因此我們只需要篩掉素數的倍數。

\(2\times 10^7\) 內,還是能跑到 \(1\) 秒以內的,再大就不行了。

時間複雜度 \(O(n \log \log n)\)

空間複雜度 \(O(n)\)

const int N = 1e7 + 7;
bool vis[N];
vector<int> prime;
void get_prime(int n) {
    for (int i = 2;i <= n;i++) {
        if (vis[i]) continue;
        prime.push_back(i);
        for (int j = 2;j * i <= n;j++) vis[i * j] = 1;
    }
}

尤拉篩(線性篩)

埃氏篩的時間複雜度已經很優秀了,但依舊會造成一個合數被篩多次的情況,我們希望每個合數都只被篩一次。因此,我們有尤拉篩,每個合數只會被其最小質因子篩掉。

證明:

假設對於 \(i \in [2,n]\) 的每個數,設其最小質因子為 \(p\) ,我們只篩到其 \(p\) 倍。

  1. 先證明任意合數 \(n\) 都能被最小質因子篩一次。

    任意合數 \(n\) ,設其最小質因子為 \(p'\) ,則 \(i = \dfrac{n}{p’}\) ,那麼有 \(p' \leq p\) ,因此 \(n\) 一定能在 \(i\)\(p\) 倍時或者之前被其最小質因子 \(p'\) 篩掉。

  2. 再證明任意合數 \(n\) 不會被非最小質因子篩掉。

    任意合數 \(n\) ,設其最小質因子為 \(p'\) ,其他任意質因子為 \(p''\), 有 \(p'' > p'\) ,則 \(i = \dfrac{n}{p''}\) 的最小質因子 \(p = p' < p''\) ,因此 \(n\) 根本不會被某個數的 \(p''\) 倍篩掉。

因此,我們對每個數只篩到其最小質因子倍,就能保證篩掉的每個數只會被其最小質因子篩一次。

時間複雜度 \(O(n)\)

空間複雜度 \(O(n)\)

const int N = 1e7 + 7;
bool vis[N];
vector<int> prime;
void get_prime(int n) {
    for (int i = 2;i <= n;i++) {
        if (!vis[i]) prime.push_back(i);
        for (int j = 0;j < prime.size() && i * prime[j] <= n;j++) {
            vis[i * prime[j]] = 1;
            if (!(i % prime[j])) break;
        }
    }
}

反素數

反素數的定義與基本性質

定義 對於正整數 \(n\) ,滿足任何小於 \(n\) 的數的因子個數都小於 \(n\) 的因子個數。

性質1 \([1,n]\) 中的反素數,一定是相同因子個數的數中最小的。

顯然對於任意 \(n \in [1,2^{31}]\) ,其質因子不會超過 \(10\) 個,且質因子的指數之和不會超過 \(31\)

性質2\(x \in [1,n],n\leq 2^{31}\) ,則 \(x\) 是反素數的必要條件是 \(x = 2^{c_1} \times 3^{c_2} \times 5^{c_3} \times 7^{c_4} \times 11^{c_5} \times 13^{c_6} \times 17^{c_7} \times 19^{c_8} \times 23^{c_9} \times 29^{c_{10}}\) ,其中 \(c_1 \geq c_2 \geq \cdots \geq c_{10} \geq 0\)

列舉反素數

根據性質2,我們可以透過dfs列舉每個質因子的指數,進而求出可能為反素數的數。再求其因子個數,根據性質1,取相同因子個數中最小的那個數即可篩選出所有反素數。

正整數結構

唯一分解定理

任何一個大於 \(1\) 的整數都可以被分解為有限個素數的乘積:

\[n = \prod_{i=1}^m p_i^{c_i} = p_1^{c_1} \times p_2^{c_2} \times \cdots \times p_m^{c_m} \]

其中 \(p_1 < p_2 < \cdots < p_m\) 為質數,\(c_i \in \Z^+\)

質因子分解

試除法

列舉 \(i \in [2,\sqrt n]\) ,一個一個除盡質因子。當然,最後至多留一個 \(> \sqrt n\) 的質因子,需要特判。

質因子不會太多(最多幾十個),所以空間當作常數。

時間複雜度 \(O(\sqrt n)\)

空間複雜度 \(O(1)\)

提前 \(O(n)\) 預處理素數

時間複雜度 \(O(\frac{\sqrt n}{\ln n})\)

空間複雜度 \(O(n)\)

void get_pfactor(int n, vector<pair<int, int>> &pfactor) {
    for (int i = 2;i * i <= n;i++) {
        if (!(n % i)) pfactor.push_back({ i,0 });
        while (!(n % i)) n /= i, pfactor.back().second++;
    }
    if (n > 1) pfactor.push_back({ n,1 });//最後可能留一個大於sqrt(n)的質數
}

Pollard-Rho演演算法

Pollard-Rho演演算法適用於快速隨機找到大數的一個非 \(1\) 因子。基於這個演演算法,我們可以利用遞迴快速分解一個大數的質因子,時間複雜度大約相同。

普通試除法的時間複雜度是 \(O(\sqrt n)\) ,對於 long long 範圍的大數是不可接受的。

Pollard-Rho的想法產生於生成隨機數 \(m \in [2,n-1]\) ,測試 \(\gcd (m,n) = d > 1\) ,來產生因子 \(d\) 的演演算法。但這種演演算法的期望複雜度是 \(O(\sqrt n \log n)\) ,比試除法還差,因此Pollard考慮利用生日悖論對隨機數作差來碰撞因子。

生日悖論:

在一年有 \(n\) 天的情況下,當房間中約有 \(\sqrt{2n\ln 2}\) 個人時,至少有兩個人的生日相同的機率約為 \(50 \%\)

更進一步地說,我們在 \([1,n]\) 內隨機生成數字,產生第一個重複數字前期望有 \(\sqrt {\dfrac{\pi n}{2}}\) 個數,約為 \(\sqrt n\) 個。

因此,假設 \(n\) 有非 \(1\) 因子 \(d\) ,我們從 \([1,n-1]\) 期望隨機抽取 \(\sqrt d\) 個數字後,就能產生兩個數 \(i,j\) 滿足 \(i - j \equiv 0 \pmod d\) ,即 \(\gcd(i-j,n) = d > 1\) 。所以,產生這樣一對數字的期望最差複雜度是 \(O(n^{\frac{1}{4}})\) ,但為了找到這些數字,需要大量互相作差並求 \(\gcd\) ,因此複雜度又回到 \(\sqrt n \log n\)

Pollard為了避免這種情況,構造了一種偽隨機序列 \(x_n = (x_{n-1}^2 + c) \bmod n\) ,其中起點 \(x_0\) 和常數 \(c\) 是隨機在 \([1,n-1]\) 中給定的。這樣的作用是,假設 \(n\) 有非 \(1\) 因子 \(d\) ,且序列存在一組數字 \(x_i,x_j\) 滿足 \(x_i - x_j \equiv 0 \pmod d\) ,那麼 \(x_{i+1} - x_{j+1} \equiv x_i^2 - x_j^2 \equiv (x_i-x_j)(x_i+x_j) \equiv 0 \pmod d\) ,即未來所有距離為 \(i-j\) 的數的差都會產生因子 \(d\)

因為這組偽隨機序列是模 \(n\) 的,因此一定會產生一個混迴圈(這也是為什麼叫做 \(\text{Rho} \to \rho\) ),所以在環上測一組,相當於測了環上所有組距離 \(i-j\) 的數,於是就不需要兩兩測試了,期望測 \(n^{\frac{1}{4}}\) 組就夠了。

此時期望複雜度是 \(O(n^{\frac{1}{4}} \log n)\) ,我們還希望把 \(\log\) 去掉,因此有了倍增最佳化。倍增最佳化的原理是:

\(\gcd (m,n) = d > 1\) ,則 \(\gcd(km,n)\geq d,k\in \Z^+\)

這意味著我們可以累積計算 \(1,2,4,8,\cdots\) 次差,乘在一起求 \(\gcd\) ,若某次作差產生非 \(1\) 因子,那麼乘積一定會產生非 \(1\) 因子,時間複雜度為 \(O(n^{\frac{1}{4}} + \log(n^{\frac{1}{4}})\log n)\) 。但是缺點是,我們倍增到最後可能由於單次積累量太大直接超過期望值太多反而變慢。實際上,我們不需要倍增多少次,假設我們取 \(dis\) 作為一次累積的量,那麼複雜度為 \(O(n^{\frac{1}{4}} + \frac{n^{\frac{1}{4}}\log n}{dis})\) ,只要 \(dis \geq \log n\) 就能做到複雜度 \(O(n^{\frac{1}{4}})\) 。在 long long 範圍內我們令 \(dis = 128\) 就足夠了,我們在倍增的基礎上每隔 \(128\) 次檢測一次即可,不到 \(128\) 次則在結束時檢測。

還有一種最佳化,Floyd判環演演算法,用於在進入迴圈時及時退出不重複跑圈。我們設兩個數 \(x,y\) ,每次判斷 \(\gcd(|x-y|,n)>1\) ,若沒有則令 \(x\) 走一步,\(y\) 走兩步。因為每次 \(y\) 多走一步,如果進入環則 \(y\) 一定能追上 \(x\) ,此時退出即可。

但實際上,判環演演算法的最佳化是不如倍增演演算法的(時間大約多一倍),且兩者不太好相容,因此我們一般使用倍增演演算法,而不使用判環演演算法。

擁有了Pollard-Rho演演算法,我們就可以對大數 \(n\) 進行質因子分解,時間複雜度大約也是 \(O(n^{\frac{1}{4}})\)

  1. 用Miller-Rabin演演算法判斷 \(n\) 是否為素數,如果是直接返回,否則進行下一步。
  2. 每次用Pollard-Rho演演算法獲得一個非 \(1\) 的因子 \(d\) ,如果為 \(n\) 就再求一次。
  3. 將數分解為 \(\dfrac{n}{d}\)\(d\) 兩個數,回到第一步遞迴進行。

時間複雜度 \(O(n^\frac{1}{4})\)

空間複雜度 \(O(1)\)

namespace Miller_Rabin {
    template<class T>
    T randint(T l, T r) {
        static mt19937 eng(time(0));
        uniform_int_distribution<T> dis(l, r);
        return dis(eng);
    }
    ll qpow(ll a, ll k, ll P) {
        ll ans = 1;
        while (k) {
            if (k & 1) ans = (__int128_t)ans * a % P;
            k >>= 1;
            a = (__int128_t)a * a % P;
        }
        return ans;
    }
    bool isPrime(ll n, int k = 10) {//8-10次
        if (n == 2) return 1;
        if (n == 1 || !(n & 1)) return 0;
        int r = __builtin_ctzll(n - 1);
        ll m = n - 1 >> r;
        while (k--) {
            ll x = qpow(randint(1LL, n - 1), m, n);
            if (x == 1 || x == n - 1) continue;//直接滿足,否則r-1次內必須有n-1
            for (int i = 1;i <= r - 1 && x != 1 && x != n - 1;i++) x = (__int128_t)x * x % n;//二次探測
            if (x != n - 1) return 0;//未經過n-1
        }
        return 1;
    }
}

namespace Pollard_Rho {
    using namespace Miller_Rabin;
    ll one_factor(ll n) {
        ll s, x = randint(1LL, n - 1), c = randint(1LL, n - 1), prod = 1;
        for (int dis = 1;;dis <<= 1) {//路徑倍增
            s = x;//固定起點作差
            for (int i = 1;i <= dis;i++) x = ((__int128_t)x * x % n + c) % n;//玄學預迴圈
            for (int i = 1;i <= dis;i++) {
                x = ((__int128_t)x * x % n + c) % n;
                prod = (__int128_t)prod * abs(x - s) % n;//累積因子
                if (i == dis || i % 128 == 0) {//固定最多128次一判
                    ll d = gcd(prod, n);
                    if (d > 1)return d;
                }
            }
        }
    }
    void get_pfactor(ll n, vector<ll> &pfactor) {
        if (isPrime(n)) {
            pfactor.push_back(n);
            return;
        }
        ll d = n;
        while (d >= n) d = one_factor(n);
        get_pfactor(n / d, pfactor);
        get_pfactor(d, pfactor);
    }
}

因數

因數的定義與基本性質

定義 若整數 \(a,b\) 滿足 \(a \mid b\) ,則稱 \(a\)\(b\) 的因數(約數,因子),\(b\)\(a\) 的倍數。

我們通常討論的因數預設為正因數,否則一定會指出。

性質1 \(n\) 的正因數有 \(\prod_{i=1}^{m}(c_i+1)\) 個。

性質2 \(n^k,k \in \Z^+\) 的正因數有 \(\prod_{i=1}^{m}(k \cdot c_i+1)\) 個。

性質3 \(n\) 的正因數和為 \(\prod_{i=1}^{m} \sum_{j=0}^{c_i} p_i^{j}\)

性質4 \([1,n]\) 內正因數集合大小約為 \(n \log n\)

性質5 \(n\) 的正因數個數上界是 \(2\sqrt n\)

但實際上這個邊界很寬鬆, \(10^9\) 內的數,因子數最多有 \(1344\) 個;\(10^{18}\) 內的數,因子數最多有 \(103680\) 個。

性質6 \(n\) 的正因數個數期望約為 \(\ln n\)

性質1到3可由唯一分解定理得到,性質4到6則由因數的定義得到,下面提供性質4,5證明。

性質4證明:

類似埃氏篩,列舉每個因子的倍數,共 \(\sum_{i=1}^n \frac{n}{i} \approx n \log n\) 個。

性質5證明:

注意到若 \(n\) 有因子 \(d\) 則一定有因子 \(\dfrac{n}{d}\) ,因此我們列舉在 \([1,\sqrt n]\) 的因子,其餘可以對稱得到,因此知道 \(2\sqrt n\)\(n\) 的因數上界。

正因數集合的求法

試除法

試除法適用於求單個正整數 \(n\) 的因數集合。

根據性質5及其證明,我們列舉因子 \([1,\sqrt n]\) 即可,因子數上界為 \(2\sqrt n\)

時間複雜度 \(O(\sqrt n)\)

空間複雜度 \(O(\sqrt n)\)

void get_factor(int n, vector<int> &factor) {
    for (int i = 1;i * i <= n;i++) {
        if (!(n % i)) {
            factor.push_back(i);
            if (i != n / i) factor.push_back(n / i);
        }
    }
}

倍數法

倍數法適用於求一個區間 \([1,n]\) 的每個數的因數集合,但不能只求出單個數的因數集合。

根據性質4,時間複雜度是 \(O(\sum_{i=1}^n \frac{n}{i}) \approx O(n \log n)\)

此法常用於一些因子相關的求和,如 \(\sum_{i=1}^n \sum_{d \mid i} d\)\(\sum_{i=1}^n \sum_{d \mid i} f(d)\) 等。

時間複雜度 \(O(n \log n)\)

空間複雜度 \(O(n \log n)\)

const int N = 1e5 + 7;
vector<int> factor[N];
void get_factor(int n) {
    for (int i = 1;i <= n;i++) {
        for (int j = 1;i * j <= n;j++) {
            factor[i * j].push_back(i);
        }
    }
}

最大公因數 \(\gcd\)

\(\gcd\) 的定義與基本性質

定義 對於正整數 \(a,b\) ,若正整數 \(d\) 是滿足 \(d \mid a\)\(d \mid b\) 的最大數,則稱 \(d\)\(a,b\) 的最大公因數(gcd,Greatest Commom Divisor),記為 \(\gcd(a,b) = d\)

約定 任何正整數與 \(0\) 的最大公因數是它本身。

互質的定義 對於正整數 \(a,b\) ,若 \(\gcd(a,b) = 1\) ,則稱 \(a,b\) 互質(互素)。

性質1 \(\gcd(a,b) = \gcd(b,a)\)

性質2 \(\gcd(a,b) = \gcd(a-b,b)\) ,其中 \(a \geq b\)

性質3 \(\gcd(a,b) = \gcd(a \bmod b,b)\)

性質4 \(\gcd(ka,kb) = k\gcd(a,b)\)

性質5 \(\gcd(a,k) = 1 \Rightarrow \gcd(a,kb) = \gcd(a,b)\)

性質6 \(\gcd(k,ab) = 1 \iff \gcd(k,a) = \gcd(k,b) = 1\)

性質7 \(\gcd(a,b,c) = \gcd(\gcd(a,b),c)\)

性質2證明:

根據整除基本性質8,\(a,b\) 的公因數和 \(a-b,b\) 的公因數相同,因此 \(\gcd\) 也相同。

推論1(性質2和5的推論) \(\gcd(a,b) = 1 \iff \gcd (a+b,a) = \gcd(a+b,b) = 1 \iff \gcd(a+b,ab) = 1\)

命題1(推論1的逆否命題的結論) \(a+b \mid ab \Rightarrow \gcd(a+b,ab) \neq 1\)

\(\gcd\) 的求法

沒有極致的效率要求的話,一般c++17及以上推薦用 std::gcd ,c++14以及更低版本推薦手寫 \(\gcd\) 。當然也可以使用 libstdc++ 實現的 __gcd ,但不能處理負數所以不安全,不過打ACM的有資料範圍就隨便用了。

輾轉相除法(歐幾裡得演演算法)

利用性質3遞迴,直到一邊為 \(0\) ,另一邊則為 \(\gcd\)

優點是一行寫完,缺點是對較大數字取模會比較慢,對於缺點可以用stein演演算法替代。

時間複雜度 \(O(\log n)\)

空間複雜度 \(O(1)\)

ll gcd(ll a, ll b) {
    return b ? gcd(b, a % b) : a;
}

更相減損術(stein演演算法)

利用性質2、4、7迭代,直到一邊減為 \(0\) ,另一邊乘上公共二次冪 \(k\) 即為 \(\gcd\)

優點是隻有加減法和位運算,比取模快。

時間複雜度 \(O(\log n)\)

空間複雜度 \(O(1)\)

ll gcd(ll a, ll b) {
    if (!a || !b) return max(a, b);
    int i = __builtin_ctzll(a), j = __builtin_ctzll(b);
    a >>= i, b >>= j;
    while (1) {
        if (a < b) swap(a, b);
        if (!(a -= b)) break;
        a >>= __builtin_ctzll(a);
    }
    return b << min(i, j);
}

最小公倍數 \(\text{lcm}\)

\(\text{lcm}\) 的定義與基本性質

定義 對於正整數 \(a,b\) ,若正整數 \(m\) 是滿足 \(a \mid m\)\(b \mid m\) 的最小數,則稱 \(m\)\(a,b\) 的最小公倍數(lcm,Least Commom Multiple),記為 \(\text{lcm}(a,b) = m\)

性質1 \(\forall a,b \in \N , \gcd(a,b) \cdot \text{lcm}(a,b) = ab\)

性質1可由 \(\gcd,\text{lcm}\) 的指數表示法證明,見下一節。

\(\text {lcm}\) 的求法

一般c++17及以上推薦使用 std::lcm ,c++14及更低版本只能手寫。

公式法

利用性質1直接求解。

時間複雜度 \(O(\log n)\)

空間複雜度 \(O(1)\)

ll gcd(ll a, ll b) {
    return b ? gcd(b, a % b) : a;
}
ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;//先除後乘避免溢位
}

\(\gcd\)\(\text{lcm}\) 的其他性質

\(\gcd\)\(\text{lcm}\) 的指數表示法

設正整數 \(n,m\geq 2\) ,則可以表示為:

\[\begin{aligned} n &= p_1^{\alpha_1} \times p_2^{\alpha_2} \times \cdots \times p_k^{\alpha_k}\\ m &= p_1^{\beta_1} \times p_2^{\beta_2} \times \cdots \times p_k^{\beta_k} \end{aligned} \]

其中 \(p_i\) 是質數,\(\alpha_i,\beta_i \in \N\)

於是有:

\[\begin{aligned} \gcd(n,m) &= p_1^{\min\{\alpha_1,\beta_1\}} \times p_2^{\min\{\alpha_2,\beta_2\}} \times \cdots \times p_k^{\min\{\alpha_k,\beta_k\}}\\ \text{lcm}(n,m) &= p_1^{\max\{\alpha_1,\beta_1\}} \times p_2^{\max\{\alpha_2,\beta_2\}} \times \cdots \times p_k^{\max\{\alpha_k,\beta_k\}} \end{aligned} \]

性質1 \(\gcd(F_n,F_m) = F_{\gcd(n,m)}\) ,其中 \(F_i\) 為斐波那契數列。

性質2 斐波那契數列相鄰兩項,”輾轉相除”次數等於“更相減損”次數

性質3 \(\gcd(a^n-b^n,a^m-b^m) = a^{\gcd(n,m)} - b^{\gcd(n,m)}\) ,其中整數 \(a \geq b\geq 0\) ,整數 \(n,m \geq 0\)\(\gcd(a,b) = 1\)

性質4 \(\gcd(a,b) = 1 \Rightarrow \gcd(a^n,b^m) = 1\) ,其中整數 \(a,b,n,m\geq 0\)

性質5

\[\gcd(C_n^1,C_n^2,\cdots,C_n^{n-1}) = \left\{ \begin{array}{l} n &,n \text{ 為素數}\\ p &,n \text{ 為只有一個質因子 } p \text{ 的非素數}\\ 1 &,n \text{ 有多個質因子} \end{array} \right . \]

性質6 \((n+1)\text{lcm}(C_n^0,C_n^1,\cdots,C_n^n) = \text{lcm}(1,2,\cdots,n+1)\) ,其中 \(n \in \N\)

推論1(性質1的推論) 斐波那契數列相鄰兩項互素。

斐波那契數列

斐波那契數列的定義與基本性質

定義

\[F_n = \left\{ \begin{array}{l} 0 &,n = 0\\ 1 &,n = 1\\ F_{n-1}+F_{n-2} &,n \geq 2 \end{array} \right . \]

性質1 \(\sum_{i=1}^n F_i = F_{n+2} -1\)

性質2 \(\sum_{i=1}^n F_{2i-1} = F_{2n}\)

性質3 \(\sum_{i=1}^n F_{2i} = F_{2n+1}-1\)

性質4 \(\sum_{i=1}^n F_{i}^2 = F_{n}F_{n+1}\)

性質5 \(F_{n+m} = F_{n-1}F_{m-1}+F_nF_m\)

性質6 \(F_n^2 = (-1)^{n-1} + F_{n-1}F_{n+1}\)

性質7 \(F_{2n-1} = F_n^2 - F_{n-2}^2\)

性質8 \(F_n = \dfrac{F_{n-2}+F_{n+2}}{3}\)

性質9 \(\lim\limits_{n\to\infty} \dfrac{F_{n+1}}{F_n} = \dfrac{\sqrt{5}-1}{2}\)

性質10 \(F_n = \dfrac{\bigg(\dfrac{1+\sqrt5}{2} \bigg)^n - \bigg(\dfrac{1-\sqrt5}{2} \bigg)^n}{\sqrt 5}\)

相關文章