原始版本 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;
}