質數判斷、質因子分解、質數篩
判斷質數常規方法
- 時間複雜度 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);
}
};