数论篇之素数详解

一.素数的定义

基本定义: 在大于1的自然数中,除了1和它本身以外不再有其他因数。

在这里插入图片描述
(此图可省略)

注意:1既不是素数也不是合数;2是最小的素数,也是唯一的偶素数。素数的个数是无限的.

二. 有关素数的定理

一:算数基本定理

任何一个大于1的正整数都能被唯一分解为有限个质数的乘积,可写作:

N=a1c1 a2c2a3c3……amcm

其中ci都是正整数,ai都是素数且满足a1

(此定理不多解释,只能意会,不能言传。)(那你还好意思称详解)

二:威尔逊定理

如果p为素数,则 ( p -1 )! ≡ -1 ( mod p )
威尔逊定理的逆定理也成立,即如果 ( p -1 )! ≡ -1 ( mod p ),则 p为素数。
(p-1)!+1一定是p的倍数
(此定理知道就行,在初等数论中,由于阶乘爆炸性增长,所以意义不大。貌似此定理不大出现)
证明的话,蒟蒻不会。但检验还是可以的
检验

        scanf("%d",&p);
	      int ans=1;
	    for(register int i=1;i<=p-1;i++){//计算p-1的阶乘
		    ans=(ans*i)%p;
	    }
	    printf("%d\n",(ans+p)%p);
	    printf("%d",(p-1)%p);

多试上几组,明显是可行的
在这里插入图片描述
在这里插入图片描述

其中首行为是不是质数。然后挨个输出

三.费马小定理

(这个定理貌似不陌生)
若p为素数,a为正整数,且a和p互质(牢记使用条件),则a^(p-1)≡1(mod p)
(讲真的,此定理我不想证明;但毕竟不能完全背离详解的题目吧。)
证明
首先,p-1个整数,a,2a,3a,……,(p-1)a中没有一个是p的倍数,因为p为素数,a为正整数,且a和p互质。

其次,a,2a,3a……,(p-1)a中没有任何两个同余与mod p的。(我觉得这很显然。。。但我不会说为么。。。。)

于是:a,2a,3a……(p-1)a 对mod p的同余既不为0(缘于首先),也没有两个同余相同(缘于其次),因此,这p-1个数对mod p的同余一定是1,2,3,……(p-1)的某一种排列(因为,同余不为零也不相同,但在mod p的情况下,结果只有0~p-1)

即:a × 2a × 3a……×(p-1)a mod p≡1×2×3×……×(p-1)(mod p) 在上述条件的情况下,这必成立~~(这不多说)~~

然后我们进行化简
即:ap-1× (p-1)!≡(p-1)! (mod p)

我们根据威尔逊定理(上一个定理),因为p是素数,所以(p-1)!和p互质(因为威尔逊定理(p-1)!+1是p的倍数,不妨设(p-1)!+1=mp,则(p-1)!=mp-1,mp-1和p互质)。所以我们可以直接约去(p-1)!。

然后我们便证明了伟大的费马小定理.(感觉身体被掏空。。。。。。)

三.素数的判定

法一:试除法

如果一个数N为非素数,则存在一个能整除N的数K,其中2<=K<=sqrt(N)

证明: 我们利用反证法,假设K不在此范围内(2<=K<=sqrt(N))

因为它是一个合数,所以在(2~N-1)内肯定会有一个数是它的约数(我们假设为M),又因为这个数不在此范围内(2<=这个数<=sqrt(N)),所以M属于[sqrt(N)+1,N-1]。不妨令 (这可能是高中数学的专用名词) K=N/M ,则肯定在此范围内了(2<=K<=sqrt(N))。
方法:我们只需扫描一下此范围内的数有没有N的约数,如果有则不是素数,反之就是素数。不难知道:时间复杂度为O(sqrt(N))

评价:代码简单,容易理解,比较好写加好像,判断单个数貌似也不慢(好像只有优点的样子) 但要判断N个数的话,恐怕就要JJ了。
代码

     inline bool judge(int n){
     if(n==1) return false;//特判1
	  for(register int i=2;i<=sqrt(n);i++){//扫描的过程
		 if(n%i==0) return false;
	 }
	 return true;
  }
法二:Miller-Rabin素性测试

这个方法其实就是利用了定理三,费马小定理的逆定理,由于费马小定理的逆定理不一定成立,所以我们要多次测试。

方法的可行性: 若N通过一次测试,则N不是素数的概率为(1、4)( 不要问我为什么,因为蒟蒻不懂 ) 若N通过t次测试,则N不是素数的概率为(1/4t),所以测试次数越多越准确。事实上t为5时,N不是素数的概率已经为(1/128),高达99.99%。(如果判断错,原因有二:一:你脸黑,二:故意卡)
方法的失误:假如随机选取四个数为2,3,5,7 ,则在2.5*1013 以内唯一一个判断失误的数为3215031751
评价小心脸黑,注意被卡! ,时间复杂度(O(tlog2(n))),貌似也不慢的样子。
代码:

#include
#include
#include
#include
#define ll long long
using namespace std;
const ll count=10;
int flag; 
ll quick_pow(ll a,ll u,ll n){//快速幂
	ll ans=1;
	while(u!=0){
		if(u%2==1) ans=ans*a%n;
		a=a*a%n;
		u=u/2;
	}
	return ans;
}
int main(){
	srand(time(NULL));//随机种子
	ll n;
	cin>>n; 
	ll a;
	if(n==2){//特判2
		cout<<"Probably a prime\n";
		return 0;
	}
	flag=0;
	for(int i=1;i<=count;i++){//测试
		a=rand()%(n-2)+2;//随机性的a
		if(quick_pow(a,n-1,n)!=1){//判断
			flag=1;break;
		} 
	}
	if(flag==0) cout<<"Probably a prime\n";//输出
	else cout<<"A composite\n";
	return 0;
}

三.素数的筛法

(求1~N之内的素数)

法一:暴力判断1(N范围内,每个都用Miller-Rabin素性测试)

评价:这个方法不建议用,即使N范围比较小。

原因一:时间复杂度高,O(N t log2n)

原因二:容错率提高了N倍 (但还是很小很小,也不算是原因吧。)

代码就不放了

法二:暴力判断2(N范围内,每个数都试除法判断)

评价:这个方法若在N范围比较小的情况下,闲的没事写写玩玩吧。

代码:

inline bool judge(int n){
	if(n==1) return false;
	for(register int i=2;i<=sqrt(n);i++){
		if(n%i==0) return false;
	}
	return true;
}
 //以下主函数里面
 for(register int i=1;i<=cnt;i++){
		if(judge(i)) printf("YES\n");//判断cnt范围内的i是不是质数
	    else printf("NO\n");
	}
法三:Eratosthenes 筛选法(埃氏筛法)

基本思想:质数的倍数一定不是素数。

方法:用一个v数组标记此数是不是合数(0表示素数,1表示非素数)。先假设所有的数都是素数(初始化v数组为零),从小到大枚举每一个素数,把x的倍数都标记为非素数(标记为一)。枚举到一个数x,若它尚未被标记,则他不能被2~x-1之间的任意数整除,该数就是素数 (这是定义啊)

注意:在枚举时我们要从二开始枚举。一的情况要特判

代码

void primes(int n){
	memset(v,0,sizeof(v));//初始化v数组
	v[1]=1;//特判1
	for(int i=2;i<=n;i++){
		if(v[i]==0)
		 for(int j=i;j<=n/i;j++)  v[i*j]=1;//标记倍数
	}
	return;
}

评价:代码简单友好写,速度也很快,埃氏筛法很常用。但个别卡这种筛法的题就没有办法了。

疑问:为什么 j循环要从(i~N/i)

上界 :因为 i的i倍的之前的数已经被其余的标记了,不需要重复标记。例如:3的2倍标记的数,已经被2的3倍给标记了。所以我们只需要标记3的(3~n/3)倍就好。
下界:范围不超过n

时间复杂度接近 O(n),为O(nloglogn)。 是一种常用的筛法。

为什么不是O(n)?

因为此筛法仍然会重复标记合数,例如12既可以被2标记,也可以被3表记。因为12=2×6,12=3×4。其根本原因是算法不能唯一确定12的产生方式。

因此,线性筛法应运而生。

法四:线性筛法

方法:我们在生成一个需要标记的合数时,每次只向现有的数乘上一个质因子(缘于原理一,唯一分解定理),并且让它是这个和数的最小质因子。(避免重复标记) 这相当于让合数的质因子从大到小累积,即让12只以 3×2×2这一种方式产生(为什么不是2×2×3,因为在统计质因子时,先是2后是3,所以6是因为3×2得来,然后枚举到6时,又×2,所以是3×2×2。(这个地方不太好懂,主要是我描述的可能不太清楚,还是看代码吧)

代码

   inline void primes(int n){
	for(register int i=2;i<=n;i++){
		if(!v[i]){
			v[i]=i;//素数的最小质因子是其本身
			prime[++m]=i;//记录素数
		}
		for(register int j=1;j<=m;j++){
			if(prime[j]>n/i||prime[j]>v[i]) break;//超出范围n或有比prime[j]更小的质因子直接终止(prime[]单调递增)
			v[i*prime[j]]=prime[j];标记最小质因子
		}
	}
	for(int i=1;i<=m;i++)
	 printf("%d ",prime[i]);//输出素数
}

顾名思义,时间复杂度O(n)
每个数只被标记一次,所以是O(n)

算法定理:唯一分解定理

评价:算法速度快,O(n)能不快吗 ,一般不会被卡吧,其实埃氏筛法我也没见到被卡的时候。

素数基本上就这点基础知识,其他的会延伸,或者把一些算法结合起来。

你可能感兴趣的:(编程)