这里写目录标题
- 一、判定质数
- 思路分析
- 代码实现
- 二、分解质因数
- 思路分析
- 典型题目
- 代码实现
- 三、质数筛
- 经典题目
- 思路分析
- 1. 朴素筛法
- 2. 埃氏筛法
- 3. 欧拉筛法
一、判定质数
思路分析
由于每个合数的因子是成对出现的,即如果
d
d
d 是
n
n
n 的因子,那么
n
d
\frac{n}{d}
dn 也是
n
n
n 的因子,故从
1
1
1 到
n
n
n 的枚举可以缩减到
1
1
1 到
n
\sqrt{n}
n,即让
d
≤
n
d
d \leq \frac{n}{d}
d≤dn,从而得到
d
≤
n
d \leq \sqrt{n}
d≤n。
代码实现
bool is_prime(int n)
{
if (n < 2) return false;
for (int i = 2; i <= n / i; ++i)
{
if (n % i == 0) return false;
}
return true;
}
时间复杂度: O ( n ) O(\sqrt{n}) O(n)
二、分解质因数
思路分析
每个合数都是由质数相乘得到的。合数可以写成质因数的乘积,这是数论中的一个基本命题。
例如:
12 = 2 * 2 * 3
18 = 2 * 3 * 3
24 = 2 * 2 * 2 * 3
① 对于任何一个合数 n n n,在它的质因数分解中,最多只会有一个质因子大于或等于 n \sqrt{n} n。
② 同时也可以推出,对任何一个合数 n n n,在它的质因数分解中,至少会有一个质因子小于或等于 n \sqrt{n} n。
典型题目
题目描述:
给定
n
n
n 个正整数
a
i
a_i
ai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。
输入格式:
第一行包含整数
n
n
n。
接下来 n n n 行,每行包含一个正整数 a i a_i ai。
输出格式:
对于每个正整数
a
i
a_i
ai,按照从小到大的顺序输出其分解质因数后,每个质因数的底数和指数,每个底数和指数占一行。
每个正整数的质因数全部输出完毕后,输出一个空行。
数据范围:
1
≤
n
≤
100
,
2
≤
a
i
≤
2
∗
1
0
9
1≤n≤100,2≤a_i≤2*10^9
1≤n≤100,2≤ai≤2∗109
输入样例:
2
6
8
输出样例:
2 1
3 1
2 3
代码实现
#define _CRT_NO_SECURE_WARNINGS
#include<iostream>
using namespace std;
void divide(int x)
{
for (int i = 2; i <= x / i; ++i) // 遍历出所有小于或等于sqrt(n)的质因子
{
if (x % i == 0)
{
int cnt = 0;
while (x % i == 0)
{
cnt++;
x /= i;
}
cout << i << ' ' << cnt << endl;
}
}
// 输出大于sqrt(n)的质因子或是质数本身
if (x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}
int main()
{
int n;
cin >> n;
while (n--)
{
int x;
cin >> x;
divide(x);
}
return 0;
}
时间复杂度:在
O
(
log
n
)
O(\log n)
O(logn) 与
O
(
n
)
O(\sqrt n)
O(n)之间
解释:若
n
n
n 为
2
2
2 的倍数,时间复杂度将会变为
O
(
log
n
)
O(\log n)
O(logn)。
三、质数筛
经典题目
题目描述:
给定一个正整数
n
n
n,请你求出
1
∼
n
1∼n
1∼n 中质数的个数。
输入格式:
共一行,包含整数
n
n
n。
输出格式:
共一行,包含一个整数,表示
1
∼
n
1∼n
1∼n 中质数的个数。
数据范围:
1
≤
n
≤
1
0
6
1≤n≤10^6
1≤n≤106
输入样例:
8
输出样例:
4
思路分析
通过将素数的倍数筛掉的方法,剩余存留的则全为质数。
筛完后 P P P 与 2 2 2 ~ ( P − 1 P-1 P−1) 之间不存在倍数关系,即 P P P 无质因子在 2 2 2 ~ ( P − 1 P-1 P−1) 之间。
1. 朴素筛法
通过将2~n的所有数的倍数筛掉的方法来得到范围内所有的质数
#define _CRT_NO_SECURE_WARNINGS
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int prime[N], cnt;
bool st[N];
void get_prime(int n)
{
for (int i = 2; i <= n; ++i)
{
if (!st[i])
{
prime[cnt++] = i;
}
for (int j = 2 * i; j <= n; j += i) st[j] = true; // 将所有质数的倍数都打上标记
}
}
int main()
{
int n;
cin >> n;
get_prime(n);
cout << cnt << endl;
return 0;
}
时间复杂度: O ( n log n ) O(n \log n) O(nlogn)
n 2 + n 3 + n 4 + . . . + + n n = n ( 1 2 + 1 3 + 1 4 + . . . + 1 n ) \frac{n}{2} + \frac{n}{3} + \frac{n}{4} + ... + + \frac{n}{n} = n(\frac{1}{2} + \frac{1}{3} + \frac{1}{4} + ... + \frac{1}{n}) 2n+3n+4n+...++nn=n(21+31+41+...+n1)
调和级数: lim n → ∞ ( 1 2 + 1 3 + 1 4 + . . . + 1 n ) = ln n + c \lim_{n \to \infty} (\frac{1}{2} + \frac{1}{3} + \frac{1}{4} + ... + \frac{1}{n}) = \ln n + c limn→∞(21+31+41+...+n1)=lnn+c (欧拉常数 c = 0.577 c = 0.577 c=0.577)
因此可得时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)
2. 埃氏筛法
优化了朴素筛法,只将2~n的质数的倍数筛掉的方法来得到范围内所有的质数
#define _CRT_NO_SECURE_WARNINGS
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int prime[N], cnt;
bool st[N];
void get_prime(int n)
{
for (int i = 2; i <= n; ++i)
{
if (st[i]) continue;
prime[cnt++] = i;
for (int j = 2 * i; j <= n; j += i) st[j] = true;
}
}
int main()
{
int n;
cin >> n;
get_prime(n);
cout << cnt << endl;
return 0;
}
时间复杂度为 O ( n log log n ) O(n \log \log n) O(nloglogn)
质数定理:在 1 1 1 到 n n n 之间的质数个数约等于 n ln n \frac{n}{\ln n} lnnn
3. 欧拉筛法
核心思想:在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的。
步骤:
- 从 i = 2 i = 2 i=2 开始,如果 i i i 还没有被筛掉,则将 i i i 加入至素数列表中。
- 遍历当前素数列表
p
r
i
m
e
[
]
prime[]
prime[],筛去
i
∗
p
r
i
m
e
[
j
]
i ∗ prime[j]
i∗prime[j]。
(保证 p r i m e [ j ] ∗ i prime[j] ∗ i prime[j]∗i 不能越界,因为越界了对结果没意义。即 i ∗ p r i m e [ j ] ≤ n i ∗ prime[j] \leq n i∗prime[j]≤n) - 当遍历到能整除 i 的素数 p r i m e [ j ] prime[j] prime[j] 时,筛去 i ∗ p r i m e [ j ] i ∗ prime[j] i∗prime[j],停止对素数列表的遍历。
- 重复 2 , 3 , 4 2,3,4 2,3,4,直到所有不超过 n n n 的整数都被遍历过素数列表中的元素即为所求的不超过 n n n 的所有素数。
#define _CRT_NO_SECURE_WARNINGS
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int cnt, prime[N];
bool st[N];
void get_prime(int n)
{
for (int i = 2; i <= n; ++i)
{
if (!st[i]) prime[cnt++] = i;
for (int j = 0; prime[j] <= n / i; j++)
{
st[prime[j] * i] = true;
if (i % prime[j] == 0) break; // 只被它的最小质因子筛选一次
}
}
}
int main()
{
int n;
cin >> n;
get_prime(n);
cout << cnt << endl;
return 0;
}
若 n < 1 0 6 n<10^6 n<106,筛和埃氏筛的时间效率差不多;若 n > 1 0 7 n>10^7 n>107,线性筛会比埃氏筛快了大概一倍。