埃氏筛

筛到一个质数后,把它所有的倍数全部删去。非常简单易懂。

复杂度 O(nloglogn)O(n \log \log n)。 (证明略去)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool prime[N];

void solve(int n){
for(int i=2;i<=n;i++)prime[i]=1;
for(int i=2;i<=n;i++){
if(!prime[n])continue;

// 优化:从质数 i 的 i 倍开始筛
// 因为 i 的 2~(i-1) 倍一定有一个更小的质数因子
// 所以已经被筛掉了
for(int j=i*i;j<=n;j+=i){
prime[j]=0;
}
}
}

线性筛(欧氏筛)

埃氏筛的效率还是不够高,我们尝试构建一个复杂度为 O(n)O(n) 的筛法。

线性筛的思路是对于一个(不一定是质数),筛掉它的一些质数倍(质数倍的质数应该小于它的最小值质因数)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int v[N]; // v[i] 是 i 的最小质因数
int prime[N];
int cnt=0;

void solve(int n){
for(int i=2;i<=n;i++){
if(v[i]==0){
v[i]=i;
prime[++cnt]=i;
}
for(int j=1;j<=cnt;j++){
// 在 oi-wiki 上,第一个条件也写作 i%prime[j]==0,这两个是等价的
if(prime[j]>v[i] || i*prime[j] > n){
break;
}
v[i*prime[j]]=prime[j];
}
}
}

在线性筛中,一个合数会且仅会被筛一遍,并且是被它的最小质因数筛掉的。

我们来证明一下为什么一个合数会且仅会被筛一遍:

  1. 先证至少会被筛一遍:

    考虑合数 N=p1p2pnN = p_1 p_2 \dots p_n(设 p1p2<pnp_1 \le p_2 \le \dots <p_n)。

    则在循环到合数 N0=p2p3pnN_0 = p_2 p_3 \dots p_n 时,一定会在第二重循环中循环到 p1p_1 (因为合数 p2pnp_2 \dots p_n 的最小质因数为 p2p_2p1p2p_1 \le p_2,循环不会跳出)

  2. 只会被筛一遍

    那么,会不会一个合数被筛了很多次呢?你可能会有疑问,合数 N=p1p2pnN = p_1 p_2 \dots p_np1p2pnp_1 \le p_2 \le \dots \le p_n)会不会在之前循环到合数 N1=p1p3p4pnN_1 = p_1 p_3 p_4 \dots p_n 时先把它乘上 p2p_2,把 NN 筛了两次呢?(此时等号 p1=p2p_1=p_2 无法取到,因为 N1N0N_1 \neq N_0

    这是不会的,因为 N1N_1 的最小值因数是 p2p_2,而 p2>p1p_2 > p_1,循环已经跳出了。这就是我们写的 prime[j]>v[i] 条件的作用。

例题

求出区间 [L,R][L,R] 之间的整数中,相邻质数的差最小值和最大值。

数据范围:1<L<R<2311 < L < R < 2^{31}RL106R-L \le 10^6

样例输入:1 11

样例输出:1 4 (2 和 3 差 1,7 和 11 差 4)

首先,筛出 2R2\sim\sqrt{R} 中所有的质数。

然后将 LRL\sim R 范围内这些质数的倍数全部删去即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<bits/stdc++.h>
using namespace std;

const int N = 1e6+5;

int cnt=0;
int v[N];
int prime[N];

void init(int n){
for(int i=2;i<=n;i++){
if(v[i]==0){
v[i]=i,prime[++cnt]=i;
}

for(int j=1;j<=cnt;j++){
if(v[i]<prime[j] || i*prime[j]>n){
break;
}
v[i*prime[j]]=prime[j];
}
}

}


bool x[N];

int main(){
int l,r;
scanf("%d%d",&l,&r);
init(sqrt(r));

for(int i=1;i<=cnt;i++){
for(int j=max(2,l/prime[i]);prime[i]*j<=r;j++){
x[j*prime[i]-l]=1;
}
}

int maxn = -1;
int minn = INT_MAX;
int last=-1;
for(int i=0;i<=r-l;i++){
if(x[i])continue;
if(last==-1){last=i;continue;}
maxn = max(maxn,i-last);
minn = min(minn,i-last);
last = i;
}

printf("%d %d\n",minn,maxn);
return 0;
}

素数定理

π(x)\pi(x) 为不超过 xx 的素数个数,则有如下近似:

π(x)xlnx\pi(x)\sim\dfrac{x}{\ln x}

以下是一张素数个数表格,供参考:

nn π(n)\pi(n) nlnn\dfrac{n}{\ln n}
10210^2 2525 2222
10310^3 168168 145145
10410^4 12291229 10861086
10510^5 95929592 86868686
10610^6 7849878498 7238272382
10710^7 664579664579 620421620421
10810^8 57614555761455 54286815428681