CSP初赛复习-18-数论基础-素数
质数 -素数
质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数(规定1既不是质数也不是合数)
素数的性质
性质1
素数的个数是无限的
性质2
4n+3型的素数也是无限的
n=0,1,2,3,4,5....
4n+3=3,7,11,15,19,23,27 ...
在上述序列里面有无限多个素数
性质3
在n>3时,n和2n之间,至少有一个素数
4 和 8 之间有素数7
7 和14之间有素数11和13
素数相关定理
1 唯一分解定理
算术基本定理,又称为正整数的唯一分解定理
每个大于1的自然数均可写为质数的积,而且这些素因子按大小排列之后,写法仅有一种方式
即 n=p1^a1 * p2^a2 * P3^a3 * Pn^an
例如
140=2^2 * 5 * 7
2 威尔逊定理
当p为质数时,(p-1)!+1能被p整除
例如 P=5时
(5-1)!+1=4 * 3 * 2 * 1 +1 =25
25能被5整除
**威尔逊定理 逆定理
** 若一个数 (p-1)!+1 能被 p 整除,那么 p 为质数
3 费马小定理
若p是素数,a是整数,且a与p互质
则:
a的(p-1)次幂被p除后余1
a^(p-1)÷p=q…1
即a^(p-1)≡1(mod p)
例如
2^10≡1(mod 11)
素数的判断
问题描述
判断一个正整数是否为素数
输入格式
一个正整数
输出格式
0 不是素数 1 是素数
输入样例
23
输出样例
1
算法1
分析
用n去除 2 ~ n-1 的每一个数,如果可以正常,则不是质数
参考程序
#include<bits/stdc++.h>
using namespace std;
bool isPrime(int n){
bool flag = true;//默认是素数
for(int i=2;i<n;i++){//2 ~ n-1 试除
if(n%i==0){//找到一个可以整除的 则不是素数
flag = false;
}
}
return flag;
}
int main(){
int n;
cin>>n;
cout<<isPrime(n);
}
/*
输入
23
输出
1
*/
时间复杂度为 O(n)
算法2
对算法1进行优化
若a为合数,则a能可以写成a=p * q ,p >1 且 q>1
如果 p<q 那么p<sqrt(a)
如果p=q 那么p=sqrt(a)
例如
a=6 则 p=2 ,q=3 p<sqrt(6)
a=9 则 p=3 ,q=3 p=sqrt(9)
从2 ~ n-1 逐一判断是否整除过程中,如果可以整除,必然此时被i整除,则必然会出现n/i的数,n和n/i是成对出现的,因此只需要判断到sqrt(n)就可以,不需要试除到n-1
参考程序
#include<bits/stdc++.h>
using namespace std;
bool isPrime(int n){
bool flag = true;//默认是素数
for(int i=2;i<=sqrt(n);i++){//2 ~ n-1 试除
if(n%i==0){//找到一个可以整除的 则不是素数
flag = false;
}
}
return flag;
}
int main(){
int n;
cin>>n;
cout<<isPrime(n);
}
/*
输入
23
输出
1
*/
时间复杂度为 O(sqrt(n))
筛法求素数
在所有大于 0 的自然数中, 除了质数就是合数. 要求质数, 只需要 "筛" 去所有的合数即可.
筛去合数要比依次判断每个数是否时质数要快的多
https://www.luogu.com.cn/problem/P3383
朴素筛法
核心思想
从2开始,对没有没筛的数(素数)逐一放入素数数组中
此素数放入数组中后对后续此素数的倍数进行打标记,可能会有重打标记
比如 6被素数2打过一次标记 ,后又被素数3打一次标记
参考程序
#include <bits/stdc++.h>
using namespace std;
const int N = 1e8 + 8;
int p[N], cnt = 0;
bool st[N];//false 可以加入质数,设置为true后不能添加 因为是前面质数的倍数
int n, q;
void get_primes(int n){//获取多大范围内的质数
for(int i=2;i<=n;i++){
if(!st[i]){//如果被其他质数标记过 不放入p数组
p[++cnt] = i;//增加一个质数i到p数组
}
//朴素筛法 无论i有没有被其他质数筛过 都有设置一次标记
for(int j=i+i;j<=n;j+=i){//对质数i的倍数进行后续标记
st[j]=1;
}
}
}
int main(){
scanf("%d %d", &n, &q);
get_primes(n);
int x;
while(q--){
scanf("%d", &x);
printf("%d\n", p[x]);
}
return 0;
}
/*
输入:
100 5
1
2
3
4
5
输出:
2
3
5
7
11
*/
时间复杂度 O(n*logn)
埃氏筛法
核心思想解决了朴素筛法重复打标记的问题
具体实现为,如果此发现此数组被打标记
1 不加入素数表
2 不进行循环打标记

参考程序
#include <bits/stdc++.h>
using namespace std;
const int N = 1e8 + 8;
int p[N], cnt = 0;
bool st[N];//false 可以加入质数,设置为true后不能添加 因为是前面质数的倍数
int n, q;
void get_primes(int n){//获取多大范围内的质数
for(int i=2;i<=n;i++){
if(!st[i]){//如果被其他质数标记过 直接标记下一个
p[++cnt] = i;//增加一个质数i到p数组
for(int j=i+i;j<=n;j+=i){//对质数i的倍数进行后续标记
st[j]=1;
}
}
}
}
int main(){
scanf("%d %d", &n, &q);
get_primes(n);
int x;
while(q--){
scanf("%d", &x);
printf("%d\n", p[x]);
}
return 0;
}
/*
输入:
100 5
1
2
3
4
5
输出:
2
3
5
7
11
*/
时间复杂度 O(n*loglogn)
线性筛 - 欧拉筛
每个素数筛一遍
然后每个素数*2再筛一遍
并且保证使用最小素数筛,发现有更小的质数筛过了,中断
参考程序
#include <bits/stdc++.h>
using namespace std;
const int N = 1e8 + 8;
//p质数数组p 欧拉筛法核心 每个合数只被最小的质数筛掉
int p[N], cnt = 0;
bool st[N];//false 可以加入质数,设置为true后不能添加
int n, q;
void get_primes(int n){
for(int i=2; i<=n;i++){
if(st[i] == 0)//从小到大 没被筛掉(说明是质数) 则添加
p[++cnt] = i;
/*
使用最小的质数 筛掉对应合数
i=2 筛掉 4 2
i=3 筛掉 6 9 2 3
i=4 筛掉 8 2 3
i=5 筛掉 10 15 25 2 3 5
i=6 筛掉 12 2 3 5
i=7 筛掉 14 21 35 2 3 5 7
i=8 筛掉 16 2 3 5 7
i=11筛掉 22 33 55 77 2 3 5 11
i=12筛掉 24 2 3 5 11
*/
/*
i*p[j]筛掉的合数需要<=n j继续++没意义 超出筛掉合数范围
保证使用最小质数筛掉合数
i%p[j]==0 最小质数 筛掉合数后break 由于p[j] 随j逐渐增大
*/
for(int j=1;i*p[j]<=n;j++){//筛掉的数在找的素数范围 否则计算无意义
st[i*p[j]]=1;//筛掉i*p[j]这个合数 i>p[j] p[j]为最小质数
/*
保证使用最小质数筛
i%p[j]!=0说明p[j]还没找到最小质数 继续j++
i%p[j]==0说明p[j]已经找最小质数 break
*/
if(i%p[j]==0)
break;
}
}
}
int main(){
scanf("%d %d", &n, &q);
get_primes(n);
int x;
while(q--){
scanf("%d", &x);
printf("%d\n", p[x]);
}
return 0;
}
时间复杂度 O(n)
作者:newcode 更多资源请关注纽扣编程微信公众号

从事机器人比赛、机器人等级考试、少儿scratch编程、信息学奥赛等研究学习

浙公网安备 33010602011771号