質數判斷、質因子分解、質數篩

n1ce2cv發表於2024-10-17

質數判斷、質因子分解、質數篩

判斷質數常規方法

  • 時間複雜度 O(根號n)
bool isPrime(long n) {
    if (n <= 1) return false;
    long sq = sqrt(n);
    for (int i = 2; i <= sq; ++i)
        if (n % i == 0)
            return false;
    return true;
}

U148828 素數判斷(Miller-Rabin模板)

判斷較大的數字是否是質數。

判斷 n 是否是質數,Miller-Rabin 測試大概過程:

1,每次選擇 1 ~ n-1 範圍上的隨機數字,或者指定一個比 n 小的質數,進行測試

2,測試過程的數學原理不用糾結,不重要,因為該原理除了判斷質數以外,不再用於別的方面

3,原理:費馬小定理、Carmichael (卡米切爾數)、二次探測定理(演算法導論 31 章)、乘法同餘、快速冪

4,經過 s 次 Miller-Rabin 測試,s 越大出錯機率越低,但是速度也會越慢,一般測試 20 次以內即可

  • 時間複雜度 O(s * ((logn) ^ 3))
#include <bits/stdc++.h>

using namespace std;

typedef __int128 ll;

// __int128 無法用 cin 讀入,只能手寫讀入函式
template<typename T>
inline T read() {
    T x = 0, f = 1;
    char ch = 0;
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
    for (; isdigit(ch); ch = getchar()) x = (x << 3) + (x << 1) + (ch - '0');
    return x * f;
}

template<typename T>
inline void write(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

template<typename T>
inline void print(T x, char ed = '\n') {
    write(x), putchar(ed);
}

// 快速冪,返回 n 的 p 次方 % mod
ll qPow(ll a, ll b, ll mod) {
    ll ret = 1;
    while (b) {
        if (b & 1) ret = (ret * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return ret % mod;
}

// 質數的個數代表測試次數,如果想增加測試次數就繼續增加更大的質數
vector<ll> p = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

// 單次測試的函式,返回 n 是不是合數
bool miller_rabin(ll n) {
    if (n < 3 || n % 2 == 0) return n == 2;
    ll u = n - 1, t = 0;
    while (u % 2 == 0) u /= 2, ++t;
    for (auto a: p) {
        if (n == a) return 1;
        if (n % a == 0) return 0;
        ll v = qPow(a, u, n);
        if (v == 1) continue;
        ll s = 1;
        for (; s <= t; ++s) {
            if (v == n - 1) break;
            v = v * v % n;
        }
        if (s > t) return 0;
    }
    return 1;
}

int main() {
    ll t = read<ll>();
    while (t--) {
        ll n = read<ll>();
        if (miller_rabin(n)) puts("Yes");
        else puts("No");
    }
    return 0;
}

質因子分解

#include <bits/stdc++.h>

using namespace std;

// 時間複雜度 O(根號n)
void printPrimeFactor(int n) {
    int sq = sqrt(n);
    for (int i = 2; i <= sq; ++i) {
        if (n % i == 0) {
            cout << i << endl;
            // 把這個因子除盡
            while (n % i == 0) n /= i;
        }
    }
    // 剩下最後一個因子
    if (n > 1) cout << n << endl;
}

int main() {
    printPrimeFactor(4012100);
}
  • 其他質因子分解方法:Pollard's rho algorithm

952. 按公因數計算最大元件大小

#include <bits/stdc++.h>

using namespace std;

class Solution {
public:
    int maxV = 100001;
    int maxN = 20001;
    // factors[a] = b: a 這個質數因子,最早被下標 b 的數字擁有
    vector<int> factors;
    // father[i] 為 nums[i] 所在集合的代表元素
    vector<int> father;
    // 記錄集合大小
    vector<int> size;

    void build(int n) {
        factors.clear();
        size.clear();
        father.clear();

        factors.resize(maxV, -1);
        father.resize(n);
        // 初始狀態以自己為集合
        for (int i = 0; i < n; ++i)
            father[i] = i;
        // 每個集合只有一個元素
        size.resize(maxN, 1);
    }

    int find(int x) {
        // 路徑壓縮
        if (x != father[x])
            father[x] = find(father[x]);
        return father[x];
    }
    
    void un1on(int a, int b) {
        int fa = find(a);
        int fb = find(b);
        if (fa == fb) return;
        // a 所在集合併入 b 所在集合
        father[fb] = fa;
        size[fa] += size[fb];
    }

    // 時間複雜度 O(n * 根號v)
    int largestComponentSize(vector<int> &nums) {
        int n = nums.size();
        build(n);

        for (int i = 0; i < n; i++) {
            int num = nums[i];
            int sq = sqrt(num);
            for (int factor = 2; factor <= sq; factor++) {
                if (num % factor == 0) {
                    if (factors[factor] == -1) {
                        // 因子 factor 最早被下標 i 的數字擁有
                        factors[factor] = i;
                    } else {
                        // 併入共同擁有因子 factor 的集合中
                        un1on(factors[factor], i);
                    }
                    // 除盡這個因子
                    while (num % factor == 0) num /= factor;
                }
            }
            if (num > 1) {
                if (factors[num] == -1) {
                    factors[num] = i;
                } else {
                    un1on(factors[num], i);
                }
            }
        }

        int res = 0;
        for (int i = 0; i < n; i++)
            res = max(res, size[i]);
        return res;
    }
};

204. 計數質數

  • 埃氏篩:時間複雜度 O(n * log(logn))
#include <bits/stdc++.h>

using namespace std;

class Solution {
public:
    // 埃氏篩統計 [0, n] 範圍內的質數個數
    // 時間複雜度 O(n * log(logn))
    int ehrlich(int n) {
        if (n <= 1) return 0;
        // visit[i] = false,代表 i 是質數,初始時認為都是質數
        vector<bool> visit(n + 1, false);
        // 遇到質數 i,就把後面到 n 位置所有以 i 為因子的合數標記成合數
        for (int i = 2, sq = sqrt(n); i <= sq; i++)
            if (!visit[i])
                for (int j = i * i; j <= n; j += i)
                    visit[j] = true;
        int res = 0;
        for (int i = 2; i <= n; i++)
            // 可以在此收集質數
            if (!visit[i]) res++;
        return res;
    }

    int countPrimes(int n) {
        return ehrlich(n - 1);
    }
};
  • 埃氏篩改進
#include <bits/stdc++.h>

using namespace std;

class Solution {
public:
    // 埃氏篩統計 [0, n] 範圍內的質數個數
    // 時間複雜度 O(n * log(logn))
    int ehrlich(int n) {
        if (n <= 1) return 0;
        // visit[i] = false,代表 i 是質數,初始時認為都是質數
        vector<bool> visit(n + 1, false);
        // 遇到質數 i,就把後面到 n 位置所有以 i 為因子的合數標記成合數
        // 估計的質數數量為奇數的個數,再算上 2 這個質數,如果發現更多合數,那麼 cnt--
        int cnt = (n + 1) / 2;
        // 跳過偶數
        for (int i = 3, sq = sqrt(n); i <= sq; i += 2) {
            if (visit[i]) continue;
            // 也要跳過偶數
            for (int j = i * i; j <= n; j += 2 * i) {
                if (!visit[j]) {
                    visit[j] = true;
                    cnt--;
                }
            }
        }
        return cnt;
    }

    int countPrimes(int n) {
        return ehrlich(n - 1);
    }
};
  • 尤拉篩:時間複雜度 O(n)
#include <bits/stdc++.h>

using namespace std;

class Solution {
public:
    // 尤拉篩統計 [0, n] 範圍內的質數個數
    // 時間複雜度 O(n)
    int euler(int n) {
        // visit[i] = false,代表 i 是質數,初始時認為都是質數
        vector<bool> visit(n + 1, false);
        // prime 陣列收集所有的質數,收集的個數是 cnt,陣列一定是遞增的
        vector<int> prime(n / 2 + 1);
        int cnt = 0;
        for (int i = 2; i <= n; i++) {
            // 沒被標記成合數,就是質數,存入 prime 陣列
            if (!visit[i]) prime[cnt++] = i;
            // 遍歷 prime 陣列
            for (int j = 0; j < cnt && i * prime[j] <= n; j++) {
                // 合數只會被他的最小質因子標記成合數
                visit[i * prime[j]] = true;
                // i 為 4,prime[j] 為 2 時:
                // 如果繼續下去,就會執行 visit[3 * 4] = true,也就是 12 被 3 標記成合數,但實際應該由 2 標記
                // i % prime[j] == 0 說明 i >= prime[j],由於 prime 陣列遞增,prime[j+1] > prime[j],可以推出 prime[j] 更小,更適合作為標記者
                // 因為由 i * prime[j+1] 得到的積,完全可以由這個比他倆都小或等於的 prime[j] 與另一個因子(是誰無所謂,反正不會比 prime[j] 還小)相乘得到
                // 4 % 2 == 0,說明 4 >= 2,由於 prime 陣列遞增,prime[j+1](也就是3) > prime[j](也就是2),可以推出 2 更小,更適合作為標記者
                // 因為由 4 * 3 得到的積 12,完全可以由這個比他倆都小的 2 與另一個因子相乘得到
                if (i % prime[j] == 0) break;
            }
        }
        return cnt;
    }

    int countPrimes(int n) {
        return euler(n - 1);
    }
};

相關文章