欧拉筛(细节分析+证明)

欧拉筛法

欧拉筛法是用线性时间求取给定范围内的素数的个数,当然素数的值也是可以记录的,适用于大范围性的素数求取,如果只是判断某一个素数,欧拉筛就显得大材小用了(费空间资源),这时候可以考虑Miller-Rabin 素数测试。

欧拉函数的时间复杂度趋于O(N)的,即使是1e8也不在话下

讲解欧拉筛之前回顾一下埃拉托斯特尼筛法
埃拉托斯特尼筛法:原理就是先找出一个素数然后把这个素数的倍数都标记为合数(一个正整数的所有倍数一定是合数,除了1),在下一次遇到被标记的数时直接跳过。
例如:
第一次遍历遇到素数2,所以(4,6,8,10…)被标记为合数
第二次遍历遇到素数3,所以(9,12,15…)被标记为合数
第三次遍历遇到合数4,因为已经被2的倍数标记所以直接跳过。
然后按照上面方法一直遍历到最后就筛选出了所有素数

代码

#include<iostream>
#include<cstring>
#include<cmath> 
using namespace std;
int prime[1000000];
/*埃拉托斯特尼筛法*/
bool vis[100000001];//1表示访问过,0表示没有访问过 
int main()
{
	memset(prime,0,sizeof(prime));
	memset(vis,false,sizeof(vis));
	int n;
	scanf("%d",&n);
	vis[1]=true;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])//是素数 
		{
			prime[++prime[0]]=i;
			vis[i]=true;
			for(int j=i*i;j<=n;j+=i)
			{
			vis[j]=true;
			}
		}
		
	} 
	printf("%d",prime[0]);
	return 0;
}

注意:上面是我对埃拉托斯特尼筛法做了一定修改,便于后面理解欧拉筛。(望dalao勿吐槽)

但是埃拉托斯特尼筛法的时间复杂度O(nlognlogn)显然对于1e8的数据是会超时的,其实筛选的过程中做了重复的操作,比如12这个数是2的倍数,也是3的倍数,就被2和3的倍数标记了两次,后面较大的数被重复标记的次数更多,为了解决重复标记的问题,于是欧拉筛排上用场了。

欧拉筛

先上代码,方便讲解

#include<iostream>
#include<cstring>
using namespace std;
const int Maxn=1e8+1;
const int inf=1e6;
int prime[100000];//存素数 
bool vis[100000001];//0代表没有访问过,1访问过 
int main()
{
	int n;
	memset(prime,0,sizeof(prime));
	memset(vis,false,sizeof(vis));
    scanf("%d",&n);
    vis[1]=true; 
    for(int i=2;i<=n;i++)
    {
    	if(!vis[i])//是素数,没被访问的都是,说明没有被标记为合数 
    	{
    		vis[i]=true;
    		prime[++prime[0]]=i;
		}
	    for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++)//以i为倍数进行筛除,对所有已知素数的倍数(也就是合数)
		{
			vis[prime[j]*i]=true;//筛除 
			if(i%prime[j]==0) break;
		} 
	}
	printf("%d",prime[0]);
	return 0;
    
}

首先解释为什么要j从1开始并且j<=prime[0]
原因:因为我们要对每一个已求出来的素数的倍数标记为合数,倍数为i,因为i会一直从2到n。

说到这里有人会问,怎么确定它就能保证不重复标记合数
这就是我们这里if(i%prime[j]==0) break;的作用,如果不退出循环当我们继续遍历到i*prime[j+1]的时候,因为i%prime[j]=0,故i=t倍prime[j],所以i*prime[j+1]=t * prime[j] * prime[j+1],相当于是prime[j]的倍数(已经被标记过),所以这样就会造成重复标记,因此我们跳出循环。

不会重复标记我们已经证明了

接下来有一个很头痛的事情就是你怎么知道不会遗漏标记呢?(我也是想了很久,才明白,本蒟蒻太痛苦了)

举个例子:在遍历标记合数是prime[3]也就是5是从==i=5(5的5倍开始)==开始标记的(prim[j]的倍数标记始终是从i=prime[j]开始的,也就是i>=prime[j]),那么5的四倍,三倍,两倍不就没有标记到吗?

(其实一点就通)分析:我们知道任何一个正整数都是可以由若干个素数的幂次的乘积构成(质因数分解),所以如果i<prime[j],那么i*prime[j]这个数一定是其他更小的素数的倍数,所以已经被标记,故我们不需要再次标记,所以这样就能够解释上面素数5的例子了。

趁热打一下铁吧
洛谷 P3912 素数个数

如果有分析不对的地方,还望dalao指出

  • 16
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值