筛法-OI-WIKI
筛法
OI-WIKI
该随笔由OI-WIKI而来,只不过添加了我对代码的注释和一些缺失的。方便以后查询。
埃氏筛
如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。
时间复杂度为:\(O(n \log{\log n})\)
int ehrlich(int n) {
vector<bool> visit(n + 1); // 默认所有数是质数
for (int i = 2; i <= n; i++) {
if (!visit[i]) { // false 代表是质数
// 从 i*i开始是因为小于i的一定被其他数标记过了
for (int j = i * i; j <= n; j += i) {
visit[j] = true;
}
}
}
int cnt = 0;
for (int i = 2; i <= n; i++)
cnt += !visit[i];
return cnt;
}
要找到直到n为止的所有素数,仅对不超过 \(\sqrt n\) 的素数进行筛选就足够了
奇数筛
因为除 2 以外的偶数都是合数,所以我们可以直接跳过它们,只用关心奇数就好。
// 奇数筛 改进埃氏筛,只能计数
int ehrlich2(int n) {
if (n <= 1) {
return 0;
}
vector<bool> visit(n + 1);
int cnt = (n + 1) / 2; // 全体奇数,(n+1)是为了应对 n=3 的特殊情况
for (int i = 3; i * i <= n; i += 2) {
if (!visit[i]) {
for (int j = i * i; j <= n; j += 2 * i) { // 每次加2i保证都是奇数
if (!visit[j]) {
visit[j] = true;
cnt--;
}
}
}
}
return cnt;
}
欧拉筛(线性筛)
埃氏筛法仍有优化空间,它会将一个合数重复多次标记。有没有什么办法省掉无意义的步骤呢?答案是肯定的。
如果能让每个合数都只被其最小质因数标记一次,那么时间复杂度就可以降到 \(O(n)\) 了。
// 欧拉筛,是每一个合数都只由它的最小质因数标记
int euler(int n) {
vector<bool> visit(n + 1);
vector<int> prime;
for (int i = 2; i <= n; i++) {
if (!visit[i]) {
prime.push_back(i);
}
for (int j = 0; j < prime.size(); j++) {
if (i * prime[j] > n) {
break;
}
visit[i * prime[j]] = true;
if (i % prime[j] ==0) { // 此后的数的最小质因数一定是 prime[j], 不能由 prime[j+1] 标记
break;
}
}
}
return prime.size();
}
- 如果
i % prime[j] ==0那么代表着 i 的最小质因数也是 prime[j],如果该氏为 false,则代表 i 与 prime[j] 互质 后续的其他拓展都依托该性质
筛法求欧拉函数
注意到在线性筛中,每一个合数都是被最小的质因子筛掉。比如设 \(p_1\) 是 \(n\) 的最小质因子, \(n'= \frac{n}{p_1}\) ,那么线性筛的过程中 \(n\) 通过 \(n' \times p_1\) 筛掉。
观察线性筛的过程,我们还需要处理两个部分,下面对 \(n' \mod p_1\) 分情况讨论。
如果 \(n' \mod p_1 == 0\) ,那么 \(n'\) 包含了 \(n\) 的所有质因子。
那如果 \(n' \mod p_1 \neq 0\) 呢,这时 \(n'\) 和 \(p_1\) 是互质的,根据欧拉函数性质,我们有:
vector<int> pri;
bool not_prime[N];
int phi[N];
void pre(int n) {
phi[1] = 1;
// i 就是 上面的 n‘
for (int i = 2; i <= n; i++) {
if (!not_prime[i]) {
pri.push_back(i);
phi[i] = i - 1; // 质数的 欧拉函数值
}
for (int pri_j : pri) {
if (i * pri_j > n) break;
not_prime[i * pri_j] = true;
if (i % pri_j == 0) {
phi[i * pri_j] = phi[i] * pri_j;
break;
}
phi[i * pri_j] = phi[i] * phi[pri_j];
}
}
}
筛法求解质因数的个数
如果只求单个数的质因数个数的话,可以使用质因数分解的套路
int f(int n) {
int ans = 0;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
ans++;
while (n % i == 0) {
n /= i;
}
}
}
if (n > 1) {
ans++;
}
return ans;
}
使用筛法求解质因数的个数是,有如下几个要点,设 \(p_1\) 为 n 的最小质因数 \(n'=\frac{n}{p_1}\),在代码中表现为 i,
- 当 \(n' \mod p_1 == 0\) 说明 \(n\) 的质因数个数和 \(n'\) 一样
factor_count[i * primes[j]] = factor_count[i]; - 当 \(n' \mod p_1 == 0\) 说明 \(n\) 的质因数个数 为 \(n'\) 的个数再加上 \(p1\)
factor_count[i * primes[j]] = factor_count[i] + 1;
vector<int> countPrimeFactors(int n) {
vector<int> is_prime(n + 1, 1);
vector<int> primes;
vector<int> factor_count(n + 1, 0); // 存储每个数的质因数个数
is_prime[0] = is_prime[1] = 0;
for (int i = 2; i <= n; ++i) {
if (is_prime[i]) {
primes.push_back(i);
factor_count[i] = 1; // 素数的质因数个数为1
}
for (int j = 0; j < primes.size() && i * primes[j] <= n; ++j) {
is_prime[i * primes[j]] = 0;
if (i % primes[j] == 0) {
factor_count[i * primes[j]] = factor_count[i]; // 不增加质因数个数
break;
}
factor_count[i * primes[j]] = factor_count[i] + 1; // 增加一个新的质因数
}
}
return factor_count;
}
筛法求约数个数
约数定理:若 \(n=\prod_{i=1}^{m}{p_i^{c_i}}\) 那么 \(d_i=\prod_{i=1}^{m}{c_i+1}\), \(d_i\) 为 i 的约数和,并且 \(d_i\) 为积性函数
证明:\(p_{i}^{c_i}\) 的约数有 \(p_i^0 + p_i^1+...+p_i^{c_i}\) ,根据乘法原理就是\(d_i=\prod_{i=1}^{m}{c_i+1}\)
vector<int> pri; // 存储素数的数组
bool not_prime[N]; // 标记是否为合数,not_prime[i]=true表示i是合数
int d[N]; // d[i]表示i的约数个数
int num[N]; // num[i]表示i的最小质因数的指数
d[1] = 1; // 1的约数只有1本身
for (int i = 2; i <= n; ++i) {
if (!not_prime[i]) { // 如果i是素数
pri.push_back(i); // 加入素数表
d[i] = 2; // 素数的约数个数为2(1和它本身)
num[i] = 1; // 素数的最小质因数指数为1(i^1)
}
// 筛去合数
for (int pri_j : pri) {
if (i * pri_j > n) break; // 超过范围则退出
not_prime[i * pri_j] = true; // 标记为合数
if (i % pri_j == 0) { // 关键部分:i包含pri_j这个质因数
num[i * pri_j] = num[i] + 1; // 最小质因数指数+1
d[i * pri_j] = d[i] / num[i * pri_j] * (num[i * pri_j] + 1);
break; // 保证每个合数只被最小质因数筛去
} else { // i不包含pri_j这个质因数
num[i * pri_j] = 1; // 新的最小质因数,指数为1
d[i * pri_j] = d[i] * 2; // 约数个数乘以2(新增一个质因数)
}
}
}
代码的实现逻辑
-
当i是素数时:
-
约数个数d[i] = 2(1和它本身)
-
num[i] = 1(因为它自己是唯一质因数,指数为1)
-
-
当i × pri_j是合数时,分两种情况:
-
i包含pri_j(i % pri_j == 0):
-
这意味着i × pri_j的最小质因数仍然是pri_j,只是指数增加了1
-
更新 num[i×pri_j] = num[i] + 1
-
约数个数更新公式:d[i×pri_j] = d[i] / (num[i]+1) × (num[i]+2)
(相当于把原来的(c+1)因子替换为(c+2))
-
-
i不包含pri_j(i % pri_j != 0):
-
这意味着pri_j是i×pri_j的最小质因数(因为pri_j比i的任何质因数都小)
-
设置num[i×pri_j] = 1
-
约数个数d[i×pri_j] = d[i] × 2
(相当于在i的约数个数基础上乘以(1+1),因为新增了一个质因数pri_j^10次和1次)
-
-
筛法求约数和
对于一个正整数 \(n\),其约数和 \(\sigma(n)\) 定义为 \(n\) 的所有正约数之和,即:
其中 \(d|n\) 表示 \(d\) 是 \(n\) 的正约数。
基于质因数分解的计算公式
若 \(n\) 的质因数分解为:
其中 \(p_i\) 是质数,\(a_i\) 是对应的指数,则约数和可以表示为:
性质
-
积性函数:若 \(m\) 和 \(n\) 互质(即 \(\gcd(m,n)=1\)),则:
\[\sigma(mn) = \sigma(m) \times \sigma(n) \] -
特殊情况:
- 对于质数 \(p\):\[\sigma(p) = 1 + p \]
- 对于质数的幂 \(p^k\):\[\sigma(p^k) = 1 + p + p^2 + \cdots + p^k = \frac{p^{k+1} - 1}{p - 1} \]
- 对于质数 \(p\):
-
完全数:若 \(\sigma(n) = 2n\),则 \(n\) 称为完全数(即等于其真约数之和的数)。
vector<int> pri; // 存储素数的数组
bool not_prime[N]; // 标记是否为合数,not_prime[i]=true表示i是合数
int g[N]; // g[i]表示i的最小质因数的等比数列和(p^0 + p^1 + ... + p^k)
int f[N]; // f[i]表示i的约数和
void pre(int n) {
g[1] = f[1] = 1; // 1的约数只有1本身,约数和为1
for (int i = 2; i <= n; ++i) {
if (!not_prime[i]) { // 如果i是素数
pri.push_back(i); // 加入素数表
g[i] = i + 1; // 素数的g值为1 + p
f[i] = i + 1; // 素数的约数和为1 + p
}
// 筛去合数
for (int pri_j : pri) {
if (i * pri_j > n) break; // 超过范围则退出
not_prime[i * pri_j] = true; // 标记为合数
if (i % pri_j == 0) { // 关键部分:i包含pri_j这个质因数
g[i * pri_j] = g[i] * pri_j + 1; // 更新等比数列和
f[i * pri_j] = f[i] / g[i] * g[i * pri_j]; // 更新约数和
break; // 保证每个合数只被最小质因数筛去
} else { // i不包含pri_j这个质因数
f[i * pri_j] = f[i] * f[pri_j]; // 约数和相乘(积性函数性质)
g[i * pri_j] = 1 + pri_j; // 新的最小质因数的等比数列和
}
}
}
}
- 更新等比数列和的代码数学原理为: 假设原数列和为 \(\frac{1-q^n}{1-q}\) 当p增加到 \(n-1\) 时,式子变为 $$\frac{1-q^{n+1}}{1-q}=\frac{q \times (1-q^n)}{1-q}+1$$\[\frac{q \times (1-q^n)}{1-q}=\frac{q-q^{n+1}}{1-q}=\frac{(1-q^{n+1})+(q-1)}{1-q}=\frac{1-q^{n+1}}{1-q}-1 \]
- 更新约数和的原理为: \(\left(1 + p_i + p_i^2 + \cdots + p_i^{a_i}\right)\) 变为 \(\left(1 + p_i + p_i^2 + \cdots + p_i^{a_i+1}\right)\) ,因此先除前者,再乘后者

浙公网安备 33010602011771号