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)

posted @ 2023-07-29 21:29  new-code  阅读(183)  评论(0)    收藏  举报