质数和约数
试除法
思想:
要检验一个数字 \(n\) 是否为质数,将 \(n\) 除以 \(1\sim \sqrt n\),如果有一个数字刚好整除 \(n\),那么 \(n\) 为合数,否则 \(n\) 为奇数。
代码:
bool prime(int x) {
if (x < 2) return false;
for (int i = 2; i <= x / i; i++) {
if (x % i == 0) {
return false;
}
}
return true;
}
埃氏筛
思想:
埃氏筛直接利用质数的定义。
我们易知一个数 \(x\) 的 \(n\) 倍(\(n \ne 1\))一定是一个合数,
那么我们遇到一个质数时 \(x\),我们可以把它的倍数 \(xn(n\ne 1)\) 全部标记为不是质数。
例:
遇到质数 \(2\)(绿色):
遇到质数 \(3\)(红色):
遇到质数 \(5\)(橙色):
遇到质数 \(7\)(蓝色):
等等。
我们还有一些优化:
-
我们遇到质数 \(x\),可以直接从 \(x^2\) 开始标记,因为前面的 \(x \cdot i\) 已经被更小的质数筛掉了,比如 \(20\) 通过 \(5 \times 4\) 筛掉,但早已经通过 \(2 \times 10\) 筛掉了。
-
我们可以只枚举 \(1 \sim \sqrt n\) 的质数就可以了,道理与试除法相同。
代码:
int n;
int prime[N], cnt;
bool is_not_prime[N];
void get_prime() {
is_not_prime[0] = is_not_prime[1] = true;
for (int i = 2; i <= n / i; i++) {
if (!is_not_prime[i]) {
for (int j = i * i; j <= n; j += i) {
is_not_prime[j] = true;
}
}
}
for (int i = 2; i <= n; i++) {
if (!is_not_prime[i]) {
prime[++cnt] = i;
}
}
}
欧拉筛
思想:
欧拉筛也叫线性筛,
它也是通过一个质数乘以一个不为 \(1\) 的数去掉合数的。
只是它规定每一个数字都是被它最小的质因子筛除。
比如 \(6\) 有质因子 \(2, 3\),但是我们只会通过 \(2\) 去筛掉 \(6\),而不是 \(3\)。
所以欧拉筛的思路就是:
- 遇到一个数字 \(x\),如果这个数字是质数,那么放入质数队列;
- 从小到大枚举质数队列中质数作为将要筛掉的数的最小质因数 \(p\),不断地用 \(p\) 乘以刚刚枚举的 \(x\) 得到数字 \(s\),将 \(s\) 标记为不是质数,直到不能保证 \(s\) 的最小质因数为 \(p\) 时停止;
现在我们想一想什么时候停止,也就是什么时候不能保证 \(s\) 的最小质因数为 \(p\)?
实际上,如果刚好 \(x \mod p\) 等于 \(0\) 时可以停止(注意此时 \(p\) 是从前往后枚举)。
因为这样,\(p\) 一定是 \(x\) 的最小质因数,那么任何大于 \(p\) 的数 \(k\),乘以 \(x\) 得到的 \(x \times k = b\) 的最小质因数也应是 \(p\),所以不能往后枚举质数,因为不能保证最小质因数是 \(p\)。
代码:
int prime[N], cnt; // 记录质数的数组
bool is_not_prime[N]; // 标记某个数字不是质数的bool数组
void get_prime(int n) {
is_not_prime[0] = is_not_prime[1] = true;
for (int i = 2; i <= n; i++) {
if (!is_not_prime[i]) prime[++cnt] = i; // 记录质数
for (int j = 1; j <= cnt && prime[j] * i <= n; j++) { // 枚举质数列表
is_not_prime[prime[j] * i] = true; // 标记不是质数
if (i % prime[j] == 0) break; // 何时停止?
}
}
}