埃氏筛
筛到一个质数后,把它所有的倍数全部删去。非常简单易懂。
复杂度 O ( n log log n ) O(n \log \log n) 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 ; for (int j=i*i;j<=n;j+=i){ prime[j]=0 ; } } }
线性筛(欧氏筛)
埃氏筛的效率还是不够高,我们尝试构建一个复杂度为 O ( n ) 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]; 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++){ if (prime[j]>v[i] || i*prime[j] > n){ break ; } v[i*prime[j]]=prime[j]; } } }
在线性筛中,一个合数会且仅会被筛一遍,并且是被它的最小质因数筛掉的。
我们来证明一下为什么一个合数会且仅会被筛一遍:
先证至少会被筛一遍:
考虑合数 N = p 1 p 2 … p n N = p_1 p_2 \dots p_n N = p 1 p 2 … p n (设 p 1 ≤ p 2 ≤ ⋯ < p n p_1 \le p_2 \le \dots <p_n p 1 ≤ p 2 ≤ ⋯ < p n )。
则在循环到合数 N 0 = p 2 p 3 … p n N_0 = p_2 p_3 \dots p_n N 0 = p 2 p 3 … p n 时,一定会在第二重循环中循环到 p 1 p_1 p 1 (因为合数 p 2 … p n p_2 \dots p_n p 2 … p n 的最小质因数为 p 2 p_2 p 2 ,p 1 ≤ p 2 p_1 \le p_2 p 1 ≤ p 2 ,循环不会跳出)
只会被筛一遍
那么,会不会一个合数被筛了很多次呢?你可能会有疑问,合数 N = p 1 p 2 … p n N = p_1 p_2 \dots p_n N = p 1 p 2 … p n (p 1 ≤ p 2 ≤ ⋯ ≤ p n p_1 \le p_2 \le \dots \le p_n p 1 ≤ p 2 ≤ ⋯ ≤ p n )会不会在之前 循环到合数 N 1 = p 1 p 3 p 4 … p n N_1 = p_1 p_3 p_4 \dots p_n N 1 = p 1 p 3 p 4 … p n 时先把它乘上 p 2 p_2 p 2 ,把 N N N 筛了两次呢?(此时等号 p 1 = p 2 p_1=p_2 p 1 = p 2 无法取到,因为 N 1 ≠ N 0 N_1 \neq N_0 N 1 = N 0 )
这是不会的,因为 N 1 N_1 N 1 的最小值因数是 p 2 p_2 p 2 ,而 p 2 > p 1 p_2 > p_1 p 2 > p 1 ,循环已经跳出了。这就是我们写的 prime[j]>v[i]
条件的作用。
例题
求出区间 [ L , R ] [L,R] [ L , R ] 之间的整数中,相邻质数的差最小值和最大值。
数据范围:1 < L < R < 2 31 1 < L < R < 2^{31} 1 < L < R < 2 31 ,R − L ≤ 1 0 6 R-L \le 10^6 R − L ≤ 1 0 6 。
样例输入:1 11
样例输出:1 4
(2 和 3 差 1,7 和 11 差 4)
首先,筛出 2 ∼ R 2\sim\sqrt{R} 2 ∼ R 中所有的质数。
然后将 L ∼ R L\sim R L ∼ 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) π ( x ) 为不超过 x x x 的素数个数,则有如下近似:
π ( x ) ∼ x ln x \pi(x)\sim\dfrac{x}{\ln x}
π ( x ) ∼ ln x x
以下是一张素数个数表格,供参考:
n n n
π ( n ) \pi(n) π ( n )
n ln n \dfrac{n}{\ln n} ln n n
1 0 2 10^2 1 0 2
25 25 25
22 22 22
1 0 3 10^3 1 0 3
168 168 168
145 145 145
1 0 4 10^4 1 0 4
1229 1229 1229
1086 1086 1086
1 0 5 10^5 1 0 5
9592 9592 9592
8686 8686 8686
1 0 6 10^6 1 0 6
78498 78498 78498
72382 72382 72382
1 0 7 10^7 1 0 7
664579 664579 664579
620421 620421 620421
1 0 8 10^8 1 0 8
5761455 5761455 5761455
5428681 5428681 5428681