质数
1.质数的定义
质数(Prime number),又称素数,指在大于 1 1 1 的自然数中,除了 1 1 1 和该数自身外,无法被其他自然数整除的数(也可定义为只有 1 1 1 与该数本身两个正因数的数)。大于 1 1 1 的自然数若不是质数,则称之为合数(也称为合成数)
2.相关例题
2.1 质数的判定
1. 朴素版
我们可以从质数的定义出发,质数是大于 1 1 1 的并且只能被1和本身整除的数。换而言之,质数就不能被 2 ∼ n − 1 2~\sim n-1 2 ∼n−1之间的数整除(假定该数为 n n n ),我们就可以由此来判断。
时间复杂度: O ( n ) O(n) O(n)
C++代码:
bool is_prime(int x){
if(x < 2) return false;
for(int i = 2;i <= n;i++){
if(x % i == 0) return false;
}
return true;
}
2. 优化版
我们注意到如果一个数 a a a 能够整除 n n n,那么 n / a n/a n/a(也就是另外一个因子)也能够整除 n n n 。例: 2 2 2 能够整除 8 8 8 ,那么 8 / 2 = 4 8 / 2 = 4 8/2=4 也能够整除 8 8 8。所以我们实际上只需要枚举到 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n)) 就能判断一个数到底是不是质数。
时间复杂度: O ( n ) O(\sqrt{n}) O(n)
C++代码:
bool is_prime(int x){
if(x < 2) return false;
//这里不用 i * i <= x 是因为 当x这个数比较大的时候,i * i 有溢出的风险
for(int i = 2;i <= x/i;i++){
if(x % i == 0) return false;
}
return true;
}
3. 例题
题目链接
题目描述
给定 n n n 个正整数 a i a_i ai,判定每个数是否是质数。
输入格式
第一行包含整数 n n n。
接下来 n n n 行,每行包含一个正整数 a i a_i ai。
输出格式
共
n
n
n 行,其中第
i
i
i 行输出第
i
i
i 个正整数
a
i
a_i
ai 是否为质数,是则输出 Yes
,否则输出 No
。
数据范围
- 1 ≤ n ≤ 100 1≤n≤100 1≤n≤100
- 1 ≤ a i ≤ 2 31 − 1 1≤ai≤2^{31}−1 1≤ai≤231−1
输入样例:
2
2
6
输出样例:
Yes
No
C++代码:
#include<bits/stdc++.h>
using namespace std;
bool is_prime(int x){
if(x < 2) return false;
for(int i = 2;i <= x/i;i++){
if(x % i == 0) return false;
}
return true;
}
int main(){
int n;
cin>>n;
for(int i = 1;i <= n;i++){
int x;
cin>>x;
if(is_prime(x)) puts("Yes");
else puts("No");
}
return 0;
}
2.2 分解质因数
1.基本思想:
唯一分解定理:一个数 n n n 肯定能被分解成 n = p 1 a 1 × p 2 a 2 × p 3 a 3 × . . . p n a n n=p_1^{a_1} \times p_2^{a_2} \times p_3^{a_3} \times... p_n^{a_n} n=p1a1×p2a2×p3a3×...pnan( p p p是素因子, a a a是素因子的个数) 因为一个数肯定是由合数和质数构成的,而合数又能被质数分解。例如: 20 = 2 × 2 × 5 20 = 2 \times 2 \times 5 20=2×2×5, 16 = 2 × 2 × 2 × 2 16 = 2 \times 2 \times 2 \times 2 16=2×2×2×2。
此外,一个数只可能存在一个 ≥ s q r t ( n ) \geq sqrt(n) ≥sqrt(n)的质因子。例如: 20 = 2 × 2 × 5 20 = 2 \times 2 \times 5 20=2×2×5, 5 5 5 就是这个 ≥ 20 \geq \sqrt{20} ≥20 的质因子。
2. 代码
时间复杂度: O ( n ) O(\sqrt{n}) O(n)
C++代码:
//该函数 会打印出x的所有质因子 已经对应的个数
void divide(int x){
//这里保证了 每次能整除x 的 i都是质数
for(int i = 2;i <= x/i;i++){
//用cnt记录本轮可能会存在的质因子个数
int cnt = 0;
//如果i 能整除 x,那么就通过这个while循环将x中的所有除掉,并记录个数
if(x % i == 0){
while(x % i == 0){
x /= i;
cnt++;
}
printf("%d %d\n",i,cnt);
}
}
//如果循环结束 x的值还>1 说明x就是剩下的唯一那个 大于 sqrt(x) 的质因子
if(x > 1) printf("%d %d\n",x,1);
}
3.例题:
题目链接
题目描述
给定 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 1≤n≤100 1≤n≤100
- 2 ≤ a i ≤ 2 × 1 0 9 2≤a_i≤2×10^9 2≤ai≤2×109
输入样例:
2
6
8
输出样例:
2 1
3 1
2 3
时间复杂度: O ( n ) O(\sqrt{n}) O(n)
C++代码:
#include<bits/stdc++.h>
using namespace std;
void divide(int x){
for(int i = 2;i <= x/i;i++){
int cnt = 0;
if(x % i == 0){
while(x % i == 0){
x /= i;
cnt++;
}
printf("%d %d\n",i,cnt);
}
}
if(x > 1) printf("%d %d\n",x,1);
}
int main(){
int n;
scanf("%d",&n);
while(n--){
int x;
cin>>x;
divide(x);
puts("");
}
return 0;
}
2.3筛质数
1.最普通的筛法
我们从 2 2 2 开始遍历,如果当前数没有被筛去,就是质数,每次循环都将小于 总数 x x x的 i i i 的倍数筛去。
例如:当 x = 8 x = 8 x=8,从 2 2 2 开始遍历
i = 2 i = 2 i=2, i i i 没有被筛去,是质数,所以记录下来。筛去 4 , 6 , 8 , 12 , 14 , 16 , 20 , . . . 4,6,8,12,14,16,20,... 4,6,8,12,14,16,20,...
i = 3 i = 3 i=3, i i i 没有被筛去,是质数,所以记录下来。筛去 6 , 9 , 12 , 15 , 18 , 21 , 24 , . . . 6,9,12,15,18,21,24,... 6,9,12,15,18,21,24,...
…
循环结束以后,所有的质数都已经被标记了
时间复杂度: O ( n × l o g n ) O(n \times logn) O(n×logn)
C++代码:
void get_prime(){
for(int i=2;i<=n;i++){
//prime数组记录质数,cnt记录质数的个数
if(!st[i]) primes[cnt++]=i;
//筛去i的倍数,就是将st[j] 置为 true
for(int j=i;j<=n;j+=i){
st[j]=true;
}
}
}
2.优化版:埃氏筛
实际上我们会发现普通的筛法做了太多重复的工作:
i = 2,筛去4,6,8,10,12,14…
i = 3,筛去6,9,12,15…
i = 4,筛去8,12,16…
…
由之前的唯一分解定理可知,一个数是肯定可以分解为若干个质数的乘积的,所以我们实际上只需要筛去每个质数的倍数就好了。
时间复杂度: O ( n × l o g l o g n ) O(n \times loglogn) O(n×loglogn)
C++代码:
void get_primes(){
for(int i=2;i<=n;i++){
//当st[i] = false时,说明此时的i是质数
if(!st[i]){
//记录质数
primes[cnt++]=i;
//筛去质数的倍数
for(int j=i;j<=n;j+=i) st[j]=true;
}
}
}
3.最终版:线性筛
通过观察我们会发现,虽然埃氏筛法相比于普通的筛法,已经减少了很多重复性的操作。但是还是存在一些重复的操作。
例如: 30 = 2 × 3 × 5 30 = 2 \times 3 \times 5 30=2×3×5;
i = 2 i = 2 i=2 时,会把 30 30 30 筛一次
i = 3 i = 3 i=3 时,也会把 30 30 30 筛一次
i = 5 i = 5 i=5 时,也会把 30 30 30 筛一次
…
实际上我们可以考虑,每次都用一个数的最小质因子来把它筛掉,后面的就直接忽略。比如 30 30 30,我们只需要 i = 2 i = 2 i=2 的时候将它筛掉, i = 3 i = 3 i=3 , i = 5 i = 5 i=5 的时候都不对它进行操作。
时间复杂度: O ( n ) O(n) O(n)
C++代码:
void get_prime(){
for(int i=2;i<=n;i++){
//记录质数
if(!st[i]) primes[cnt++]=i;
//筛去小于等于n 的质数的倍数
for(int j=0;primes[j]<=n/i;j++){
st[primes[j]*i]=true;
//因为我们是从小到大枚举的,所以primes数组的中的质数也是 2,3,5,7....
//所以当我们第一次遇到 i % primes[j]==0时,说明此时的
//primes[j]就是i的最小质因子,上面的那句st[primes[j]*i]=true
//已经通过这个最小质因子筛去了对应的合数
//所以我们就忽略后面的操作 直接退出循环就好
if(i%primes[j]==0) break;
}
}
}
4.例题
题目链接
题目描述
给定一个正整数 n n n,请你求出 1 ∼ n 1\sim n 1∼n 中质数的个数。
输入格式
共一行,包含整数 n n n。
输出格式
共一行,包含一个整数,表示 1 ∼ n 1 \sim n 1∼n 中质数的个数。
数据范围
- 1 ≤ n ≤ 1 0 6 1≤n≤10^6 1≤n≤106
输入样例:
8
输出样例:
4
时间复杂度: O ( n ) O(n) O(n)
C++代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
int st[N],prime[N];
int main(){
int n;
cin>>n;
int cnt = 0;
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] = 1;
if(i % prime[j]==0) break;
}
}
printf("%d\n",cnt);
return 0;
}