AcWing874-线性筛选质数
题解
这一题看起来很简单,不就是质数吗?但是要在$O(n)$的时间复杂度内实现,还是比较难理解的,在此之前会介绍朴素筛法和埃氏筛法,最后介绍线性筛法。
一、朴素筛法
朴素筛法相对比较简单,在 i 枚举 1 - n 的过程中,不断筛掉 i 对应的倍数即可,这样的确可以把所有的合数都筛掉,但是会有大量重复筛的过程,例如 2 会筛 12,3 会筛 12,6 也会筛12,时间复杂度估计为$O(nlog(n))$。
#include <iostream> #include <cstring> using namespace std; const int MAXN = 1000010; int n; bool st[MAXN]; void primes(int n) { int cnt = 0; for (int i = 2; i <= n; ++i) { if (!st[i]) { cnt++; } // 筛 i 对应的倍数 for (int j = i; j <= n; j += i) { st[j] = true; } } cout << cnt << endl; } int main() { cin >> n; memset(st, false, sizeof(st)); primes(n); return 0; }
二、埃氏筛法
埃氏筛法是在朴素筛法的基础上进行了优化,即只筛质数对应的倍数即可,因为所有的合数都可以拆成质数的乘积,当然,这类筛法还是存在重复筛的过程,例如 2 会筛 6,3 也会筛 6,时间复杂度估计为$O(nloglog(n))$。
#include <iostream> #include <cstring> using namespace std; const int MAXN = 1000010; int n; bool st[MAXN]; void primes(int n) { int cnt = 0; for (int i = 2; i <= n; ++i) { if (!st[i]) { cnt++; // 只筛质数,因为其它数都已经被筛过 for (int j = i; j <= n; j += i) { st[j] = true; } } // 筛 i 对应的倍数 } cout << cnt << endl; } int main() { cin >> n; memset(st, false, sizeof(st)); primes(n); return 0; }
三、线性筛法
线性筛法可以保证每个数都只会被筛一次,时间复杂度为$O(n)$,那为何呢?
在筛的过程中,我们是用最小质因子去筛选,在此每个合数都只会被最小质因子筛掉且被筛一次,分两种情况进行讨论。
1、i % pj == 0,pj 为 i 的最小质因子,pj 也为 i * pj 的最小质因子。
2、i % pj != 0,pj 小于 i 的最小质因子,但 pj 是为 i * pj 的最小质因子。
此时的 i * pj 就可以被筛掉,且每一个合数有且只有一个最小质因子,因此,每一个合数都只会被其最小质因子筛掉且被筛一次。
#include <iostream> #include <cstring> using namespace std; const int MAXN = 1000010; int n; int prime[MAXN]; bool st[MAXN]; // 线性筛法 void primes(int n) { int cnt = 0; for (int i = 2; i <= n; ++i) { if (!st[i]) { prime[cnt++] = i; } // 从小到大枚举质数 // 每个合数都只会被其最小质因子筛一次 for (int j = 0; prime[j] <= n / i; ++j) { st[prime[j] * i] = true; if (i % prime[j] == 0) { break; } } } cout << cnt << endl; } int main() { cin >> n; memset(st, false, sizeof(st)); primes(n); return 0; }
好啦,最后一种筛法很重要哦!