对于整数而言:
前言
如何判断[2,n]之间每个数是质数还是合数呢?
首先我们可以枚举每一个数,用试除法判断它是质数还是合数。时间复杂度O(
n
n
n \sqrt n
nn)。
事实上还有更好的做法。
埃氏筛求质数
我们枚举到每一个数x,就从二倍开始枚举它的所有倍数{2x,3x,…},标记它的倍数,它的倍数一定不是质数。
如果枚举到一个数x时,它还没被标记,证明它在[2,x-1]范围内没有约数,也即它是质数。
其中vis[i]==true,表示i为合数:
这就是朴素的埃氏筛法。
3大概有
n
3
\frac n 3
3n个倍数,4大概有
n
4
\frac n 4
4n个倍数,i大概有
n
i
\frac n i
in个倍数。
每个数i都会枚举它的所有倍数,也就是枚举倍数次。
时间复杂度为O(
∑
i
=
2
n
n
i
\sum_{i=2}^n{\frac n i}
∑i=2nin)。
∑
i
=
2
n
n
i
\sum_{i=2}^n{\frac n i}
∑i=2nin是一个调和级数,约等于O(
n
ln
n
n\ln n
nlnn)=O(
n
log
e
n
n\log_e n
nlogen)=O(nlogn)。
时间复杂度也就是O(nlogn)
时间复杂度带有log是因为,一些数字被重复标记了。
事实上可以优化:
我们注意到,每个合数n都可以表示为
p
⋅
k
p \cdot k
p⋅k的形式,其中p是n的一个质因子。
那么我们优化,只枚举质数的倍数,然后标记它。
很容易证明,这样筛的时间复杂度是O(
n
ln
ln
n
n\ln\ln n
nlnlnn)=O(nloglogn)。
这个就是埃氏筛(埃拉托色尼筛法)。
额外提一嘴,我们注意到,同一个数的相同质因子只算一次的话。[1,n]中所有数的质因子数目之和为O(nloglogn)。
因为我们注意到,在埃氏筛的过程中,每个数都被它的每个不同质因子筛到一次。
线性筛(欧拉筛)
我们注意到,即使是优化了之后的埃氏筛法也有算重的地方,因为一个数被它的所有不同质因子到。
如果想要获得更好的时间复杂度,我们只需要强制每一个数只被它的其中一个质因子筛到一次。
最简单的一种写法是,我们只让一个数被它的最小质因子筛到一次。
const int N=100000000;
bool vis[N+5];
vector<int> a;
void Euler(int n) {
for(int i=2;i<=n;i++) {
if(!vis[i])
a.push_back(i);
for(auto&j:a) {
int x=i*j;
if(x>N) break;
vis[x]=true;
if(i%j==0) break;
}
}
}
我们指定i*j的最小质因子必须为j。
如果i%j=0,表示i中含有j这个因子,这意味着
i
×
j
i \times j
i×j本身的最小质因子还是j,筛
i
×
j
i \times j
i×j本身是合法的。
但是下一次循环,由于质数是从小到大被筛出来的,也就是说质数j是升序存储的,j会变大,此时 新的
i
×
j
i \times j
i×j 的最小质因子就是i的最小质因子(也就是原来的j)了,违反了我们的规定,因此退出循环。
每个数都仅被它的最小质因子筛到一次。时间复杂度O(n)。
后记
于是皆大欢喜。