原始版本 O ( n n ) O(n\sqrt n) O(nn)
1.比质数p小的数都和p互质。
2.如果一个数x存在两个因子n, m,那一定满足 n < = x 且 m > = x n <=\sqrt x且m>=\sqrt x n<=x且m>=x或者 n > = x 且 m < = x n >=\sqrt x且m<=\sqrt x n>=x且m<=x
那我们只要验证x是否为素数,我们就可以在 [ 2 , x ] [2,\sqrt x] [2,x]范围中寻找就足够了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+8;
ll p1[N], u;
bool st[N];
int main() {
ll x = 0;
bool f;
for(int i=2; i<N; i++) {
f = true;
for(int j=2; j<sqrt(i)+1; j++) {
x ++;//记录循环次数
if(i != j && i%j == 0){
f = false;
break;
}
}
if(f) p1[u++] = i;
}
cout << u << ' ' << x << endl;
//u = 9593; x = 2755713;
return 0;
}
埃氏筛法 O ( n l o g l o g ( n ) ) O(nloglog(n)) O(nloglog(n))
埃氏筛法的主要思路:
i i i从2开始枚举,将 i i i之后的所有 i i i的倍数 ( i ∗ j < N ) (i*j<N) (i∗j<N)都标记。在枚举到未被标记的 i i i时,说明小于i的元素中没有 i i i的倍数,等价于 i i i就是素数。
优化版本:将要被的标记元素从 i ∗ i i*i i∗i开始,每次加 i i i,相当于 i ∗ ( i + j ) i*(i+j) i∗(i+j)。
我们来列举一下。
2 ∗ 2 3 ∗ 3 4 ∗ 4 5 ∗ 5 2*2~~~~3*3~~~~4*4~~~~5*5 2∗2 3∗3 4∗4 5∗5
2 ∗ 3 3 ∗ 4 4 ∗ 5 5 ∗ 6 2*3~~~~3*4~~~~4*5~~~~5*6 2∗3 3∗4 4∗5 5∗6
2 ∗ 4 3 ∗ 5 4 ∗ 6 5 ∗ 7 2*4~~~~3*5~~~~4*6~~~~5*7 2∗4 3∗5 4∗6 5∗7
2 ∗ 5 3 ∗ 6 4 ∗ 7 5 ∗ 8 2*5~~~~3*6~~~~4*7~~~~5*8 2∗5 3∗6 4∗7 5∗8
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .......~~~~.......~~~~.......~~~~....... ....... ....... ....... .......
我们能够发现, i ∗ i i*i i∗i之前的 i ∗ ( i − j ) j > 0 i*(i-j)~~j>0 i∗(i−j) j>0的倍数都会被 ( i − j ) ∗ i (i-j)*i (i−j)∗i标记,我们在这里减少了重复标记的次数。
优化中还有一个小问题:为啥 i i i只需要枚举到 N \sqrt N N就行了i<sqrt(N)+1
因为我们在第二层循环中是从 i ∗ i i*i i∗i开始,到N结束,所以我们能够标记区间 [ 2 , N ] [2, N] [2,N]的全部元素
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+8;
ll p2[N], p3[N], p4[N], u;
bool st[N];
int main() {
ll x = 0;
for(int i=2; i<N; i++) {
if(!st[i]){
p2[u++] = i;
for(int j=2; j*i<N; j++) {
st[j*i] = true;
x++;
}
}
}
cout << u << ' ' << x << endl;
//u = 9593; x = 256826;
//优化
memset(st, 0, sizeof st);
u = x = 0;
for(int i=2; i<sqrt(N)+1; i++) {
if(!st[i]){
for(int j=i*i; j<N; j+=i) {
st[j] = true;
x++;
}
}
}
for(int i=2; i<N; i++) {
if(!st[i]) p3[u++] = i;
}
cout << u << ' ' << x << endl;
//u = 9593; x = 193091;
return 0;
}
线性筛 O ( n ) O(n) O(n)
这条语句是减少复杂度的关键if(i % p4[j] == 0) break;
我们来列举一下被标记元素 i ∗ p 4 [ j ] i * p4[j] i∗p4[j]变化过程:
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|
2*2 | 3*2 | 4*2 | 5*2 | 6*2 | 7*2 | 8*2 | 9*2 | 10*2 |
3*3 | 5*3 | 7*3 | 9*3 | |||||
5*5 | 7*5 | |||||||
7*7 |
加入把关键句去掉,将会出现 4 ∗ 3 , 6 ∗ 3 4*3,6*3 4∗3,6∗3等。
但我们在后面寻找可以发现 4 ∗ 3 < = > 6 ∗ 2 , 6 ∗ 3 < = > 9 ∗ 2 4*3<=>6*2,6*3<=>9*2 4∗3<=>6∗2,6∗3<=>9∗2,这样就减少了重复标记的次数。
这样就保证了每个合数只会被它的最小质因数标记,所以时间复杂度为 O ( N ) O(N) O(N)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+8;
ll p4[N], u;
bool st[N];
int main() {
ll x = 0;
memset(st, 0, sizeof st);
u = x = 0;
for(int i=2; i<N; i++) {
if(!st[i]) p4[u++] = i;
for(int j=0; j<u && i*p4[j]<N; j++) {
x++;
st[i*p4[j]] = true;
if(i % p4[j] == 0) break;
//在删掉这条语句之后u = 9593, x = 193091;
//证明这条语句是减少复杂度的关键。
}
}
cout << u << ' ' << x << endl;
//u = 9593; x = 90413;
return 0;
}
三合一代码。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+8;
ll p1[N], p2[N], p3[N], p4[N], u;
bool st[N];
int main() {
ll x = 0;
bool f;
for(int i=2; i<N; i++) {
f = true;
for(int j=2; j<sqrt(i)+1; j++) {
x ++;
if(i != j && i%j == 0){
f = false;
break;
}
}
if(f) p1[u++] = i;
}
cout << u << ' ' << x << endl;
u = x = 0;
for(int i=2; i<N; i++) {
if(!st[i]){
p2[u++] = i;
for(int j=2; j*i<N; j++) {
st[j*i] = true;
x++;
}
}
}
cout << u << ' ' << x << endl;
memset(st, 0, sizeof st);
u = x = 0;
for(int i=2; i<sqrt(N)+1; i++) {
if(!st[i]){
for(int j=i*i; j<N; j+=i) {
st[j] = true;
x++;
}
}
}
for(int i=2; i<N; i++) {
if(!st[i]) p3[u++] = i;
}
cout << u << ' ' << x << endl;
memset(st, 0, sizeof st);
u = x = 0;
for(int i=2; i<N; i++) {
if(!st[i]) p4[u++] = i;
for(int j=0; j<u && i*p4[j]<N; j++) {
x++;
st[i*p4[j]] = true;
if(i % p4[j] == 0) break;
}
}
cout << u << ' ' << x << endl;
return 0;
}