筛法,个人觉得在基础阶段牢固的掌握埃氏筛和欧拉筛就很好了。它们是很好用的工具,用途也很广泛,甚至会用在一些(我)意想不到的地方。
筛法
1. 埃氏筛
- 主要思想:筛掉所有质数的倍数
- 代码
// 为什么j可以从i*i开始?
// 假设i=7, 那么比i小的所有的质数, 已经把2*7,3*7,5*7这样的数筛掉了, 所有可以直接从7*7开始筛
void Eratosthenes(){
for (int i=2;i<=n;++i) st[i] = 1;
for (int i=2;i<=n;++i){
if (st[i]){
p[++cnt] = i;
if ((ll)i*i > n) continue;
for (int j=i*i;j<=n;j+=i){
st[j] = false;
}
}
}
}
-
时间复杂度分析
如果是筛所有的数的倍数,那么会筛 N 2 + N 3 + . . . + N N = N × ( 1 2 + 1 3 + . . . + 1 N ) \frac{N}{2}+\frac{N}{3}+...+\frac{N}{N}=N\times(\frac{1}{2}+\frac{1}{3}+...+\frac{1}{N}) 2N+3N+...+NN=N×(21+31+...+N1) 次。
由于调和级数 1 + 1 2 + 1 3 + . . . + 1 N = l n ( N + 1 ) + r 1+\frac{1}{2}+\frac{1}{3}+...+\frac{1}{N}=ln(N+1)+r 1+21+31+...+N1=ln(N+1)+r, r r r为欧拉常数, r ≈ 0.5772156649 r\approx 0.5772156649 r≈0.5772156649。因此,时间复杂度可近似为 O ( N l n N ) < O ( N l o g N ) O(NlnN)<O(NlogN) O(NlnN)<O(NlogN)。
当只筛质数时,由质数分布定理可得 N N N中约有 π ( N ) = N l n N \pi(N)=\frac{N}{ln N} π(N)=lnNN个质数,因此时间复杂度估算结果为 O ( N l n N l n N ) O(\frac{NlnN}{lnN}) O(lnNNlnN),即非常接近 O ( N ) O(N) O(N)。在实际计算下,埃氏筛的时间复杂度为 O ( N l o g l o g N ) O(NloglogN) O(NloglogN)。
2. 欧拉筛
- 主要思想:每个合数只被筛一次的算法。
- 代码
void Euler(){
for (int i=2;i<=n;++i){
if (!st[i]) p[++cnt] = i;
for (int j=1;p[j]<=n/i;++j){
st[p[j] * i] = true;
if (i % p[j] == 0) break;
}
}
}
// 为什么每个合数只会被筛一次呢?
// 因为每个合数只会被它的最小质因子筛掉
// 从小到大枚举p[j],当i%p[j]==0时, break。
// 1. 当i%p[j]==0时。说明p[j]是i的最小质因子, 那么p[j]一定也是p[j]*i的最小质因子
// 2. 当i%p[j]!=0时, 说明p[j]比i的最小质因子还小, 那么p[j]一定是p[j]*i的最小质因子
- 时间复杂度: O ( N ) O(N) O(N)。