[总结] 筛法
埃筛
-
考虑这样一件事情:如果 \(x\) 是合数,那么 \(x\) 的倍数也一定是合数。利用这个结论,我们可以避免很多次不必要的检测。
-
如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。
代码实现
- 先把所有的数都看成素数,然后从 \(2\) 开始筛去倍数就可以了
int Eratosthenes(int n){
for(int i=0;i<=n;i++)isp[i]=true;
isp[1]=isp[0]=false;
for(int i=2;i<=n;i++){
if(isp[i]){
primes[++cnt]=i;
for(int j=i*i;j<=n;j+=i){//可以保证i*i之前的已经筛完了
isp[j]=false;
}
}
}
}
-
复杂度是 \(O(nloglogn)\)
-
从 \(i* i\) 开始筛可以显著优化,但不会改变复杂度
线性筛
- 与埃筛不同的是,保证每个合数都在它的最小因数筛掉它
代码实现
- 可以枚举当前 \(primes\) 数组里存储的质数,然后结合 \(i\) 筛去 \(i* primes[j]\)
考虑这么一个问题:
在结合的时候如果 $i \ mod \ primes[j]=0 $,那么说明 $i* primes[j] $ 这个数一定被 \(primes[j]\) 约去了,再枚举 \(primes\) 的元素就不行了,直接break就可以
void Segment(int n){
notp[1]=true;
for(int i=2;i<=n;i++){
if(!notp[i])primes[++cnt]=i;
for(int j=1;j<=cnt;j++){
int tmp=primes[j]*i;
if(tmp>n)break;
notp[tmp]=true;
if(i%primes[j]==0)break;
}
}
return;
}
区间筛
求一段区间 \([L,R]\) 的素数
- 利用埃筛思想
代码实现
-
先筛出 \(\sqrt{n}\) 以内的小素数
-
利用埃筛思想把小素数在 \([L,R]\) 中的倍数筛掉
细节部分
枚举小素数的时候要保证所筛的数的开始是 \([L,R]\) 以内的小素数的最小倍数。
可以用:
(l+x-1)/x*x;
板子题:
void Block(int n){
notp[1]=true;
for(int i=2;i<=n;i++){
if(!notp[i])primes[++cnt]=i;
for(int j=1;j<=cnt;j++){
int tmp=primes[j]*i;
if(tmp>n)break;
notp[tmp]=true;
if(i%primes[j]==0)break;
}
}
return;
}
void solve(int l,int r){
for(int i=l;i<=r;i++)isp[i-l]=1;
if(l==0)isp[0]=isp[1]=0;
else if(l==1)isp[1]=0;
for(int i=1;i<=cnt;i++){
int x=primes[i];
if(x*x>r)break;
for(int j=(l+x-1)/x*x;j<=r;j+=x){
if(j==x)continue;
isp[j-l]=false;
}
}
}

浙公网安备 33010602011771号