欧拉线性筛

筛质数

关于欧拉筛筛质数,其总体思想:
· 首先,假设所有的数都是质数,然后通过筛选将合数一一筛去
· 为了确保可以在线性时间内筛去所有的合数(即对于每一个数只处理一次),每一个合数只由其最小的质因数筛去一次,从而避免一个合数被多次筛去而造成浪费时间。

那么,具体的实现思路如下:

  1. 标记所有的数字为质数不用多说,开一个数组,所有数字记录为true即可;
  2. 用一个for循环遍历每一个数字;(注意一下,遍历从“2”开始,因此“1”应当初始化为false);
  3. 假设当前这个数字是质数,即遍历到这个数字的时刻,这个数字仍然被标记为true,则记录这个质数,存入数组中;
  4. 不论当前数字是质数还是合数,都要进行如下操作。易知,假设一个数字可以表示为两个大于“1”的数字的乘积的形式,那么这个数字一定是合数。因此,接下来就要筛掉所有因数中含该数字的合数。

这是核心部分,步骤4的具体代码:

for(int j=1;j<=prime[0]&&prime[j]*i<=size;++j){
	p[prime[j]*i]=false;
	if(i%prime[j]==0) break; //核心中较难理解的部分
}

挨行解释:
第一行:关于for循环的条件,一是要如果条件允许就要遍历每一个已知的质数;二是假如两者乘积不能大于约定的范围,毕竟超范围了,“范围”这个东西就没意义了吗。
第二行:这个就是筛掉这两个数字的积的合数。
第三行:这个就是一个关键,有了它才能保证线性时间。为什么当i是prime[j]就要break掉呢?欧拉筛的一个重要思想就是要求每一个合数都要由它的最小质因数筛去。假设当前的I是prime[j]的倍数,那么i=n×prime[j] (n∈N*)一定成立。假如此刻不break掉,那么下一个筛到下一个质数,prime[j+1]时,用同样的i,需要筛去I×prime[j+1],而i=n×prime[j],因此I×prime[j+1]=n×prime[j]×prime[j+1],很明显,这个合数的最小质因数是prime[j],而非prime[j+1],因此也就应该由prime[j]×某一个数字筛去,而不应该由prime[j+1]×i筛去,这样就会与欧拉筛的思想不符合,导致时间复杂度增加。

至此,欧拉筛的筛质数部分就完成了,下面是完整代码(以筛100以内的质数为例):

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

int phi[101],prime[101];
bool p[101];

int main(){
	for(int i=2;i<=100;++i) p[i]=true;
	for(int i=2;i<=100;++i){
		if(p[i]){
			prime[++prime[0]]=i;
		}
		for(int j=1;j<=prime[0]&&prime[j]*i<=100;++j){
			p[prime[j]*i]=false;
			if(i%prime[j]==0) break;
		}
	}
	return 0;
}

欧拉函数

在筛出质数的同时,我们也可以同时求出来每一个数字的欧拉函数。

科普一下:
欧拉函数指对于一个数字,对于小于等于该数字的所有正整数,与该数字互质的数字的个数。符号φ,中文读音“斐",英文写法:phi
特别地规定:φ(1)=1.

那么,为了求可以在可以在线性时间内求出来所有的欧拉函数,需要事先知道三个性质:
对于一个质数p:
性质1:φ(p)=p-1;
因为p为质数,而从1到p,只有p与p自本身不互质,其余的(p-1)个数字都与p互质,因此得出性质1;
性质2:对于i%p==0 , φ(ip)=pφ(i);
性质3:对于i%p!=0,φ(ip)=φ(i)(p-1);
利用了欧拉函数的积性。而又通过性质1可知,p-1=φ(p),因此φ(ip)=φ(i)(p-1)=φ(i)φ(p);
这个,本人才疏学浅,看了各种证明,但还是不明白性质2,性质3是怎么得出来的,所以先当作结论记住吧。
知道了这三个结论之后,在配合欧拉筛筛质数,就可以得出范围内所有的数字的欧拉函数。
代码:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

int phi[101],prime[101];
bool p[101];

int main(){
	phi[1]=1;
	for(int i=2;i<=100;++i) p[i]=true;
	for(int i=2;i<=100;++i){
		if(p[i]){
			prime[++prime[0]]=i;
			phi[i]=i-1;//改动部分1(对应性质1)
		}
		for(int j=1;j<=prime[0]&&prime[j]*i<=100;++j){
			p[prime[j]*i]=false;
			if(i%prime[j]==0){
				phi[i*prime[j]]=prime[j]*phi[i];//改动部分2(对应性质2)
				break;
			}
			else{
				phi[i*prime[j]]=phi[i]*phi[prime[j]];//改动部分3(对应性质3)
			}
		}
	}
	return 0;
}

这个应该挺浅显易懂的,不过多解释了
一个小补充:该算法怎么保证筛完之后,所有数字的phi值都被更新?
因为每一个数字要么是质数,要么是合数。
假如筛去了一个数字,即判断该数字是合数,紧接着在接下来的代码中,就将该数字分类(性质1和性质2),并更新其phi值。
但是,假如一个数字被判断为质数,那么该数字phi值就会直接被更新为该数字-1,根据性质1.

posted @ 2020-06-29 22:24  ticmis  阅读(171)  评论(0编辑  收藏  举报