洛谷p1835 素数密度 (线性欧拉筛,埃式筛)

在这里插入图片描述

题目分析

难度:普及+/提高
暴力40分做法: 从L到R进行枚举,然后判断是否为素数!
但这样显然是不对的,要结合筛法来解决此题,虽然L,R的范围是整个int的取值,但是两者的差值为1e6,可以从此突破!

预备知识 (解决一个问题:求出不大于n的素数的个数)

朴素筛法:时间复杂度 O(n根号n)

//普通朴素算法 求质数  ( n根号n的时间复杂度 ) 
void Or_Prime(int n)
{
	for(int i=2;i<=n;i++)
	{
		int flag = 0;
		for(int j=2;j<=sqrt(i);j++)
			{
				if(i%j==0) 
					{
						flag = 1;
						break;
					}	
			}	
		if(flag==0) Prime[++cnt] = i;
	}	
} 

埃拉托斯特尼筛法 时间复杂度: O(nloglogn)

要得到自然数n以内的全部素数,必须把不大于的所有素数的倍数剔除,剩下的就是素数。
给出要筛数值的范围n,找出以内的素数。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个质数5筛,把5留下,把5的倍数剔除掉;不断重复下去…。

bool isPrime[n];  int Prime[n];
void Er_Prime(int n)
{
	for(int i=1;i<=n;i++)
		isPrime[i] = 1;  //初始化所有数均为素数
	for(int i=2;i<=n;++i)
	{
		if(isPrime[i]==1) 
			{  //如果i是素数
				Prime[++cnt] = i; //则用Prime数组存储该素数,cnt++ 
				for(int j = i*i ;j<=n&&j>=1;j+=i)     //注意如果i=50000,则j=i*i会int越界,导致j为负数,从而强行终止此程序!!! 
				{    //枚举质数i的所有倍数,且从 i*i 开始!!!如果是2*i的话,在i==2的时候已经筛过了 
					isPrime[j] = 0;
				} 
			}
	}
} 

欧拉线性筛 时间复杂度: O(n)
在埃氏筛的基础上,保证每一个合数只被筛选一次!!!

//欧拉筛法 
void Euler_Prime(int n)
{
	for(int i=1;i<=n;i++)
		isPrime[i] = 1;    //同样初始化所有的数均为素数
	for(int i=2;i<=n;i++)
	{
		if(isPrime[i]==1)
			Prime[++cnt] = i;  //如果i是素数,则用Prime数组储存下来,并且cnt计数++
		for(int j=1;j<=cnt&&i*Prime[j]<=n;j++) //枚举所有的素数,筛掉所有素数的倍数
			{
				isPrime[i*Prime[j]] = 0;
				if(i%Prime[j]==0) break;	//i在之前已经被筛过了!
			}	
	} 
}

有了筛法的基础,那么接下来的思想就是找出所有位于【2,sqrt(R)】 之间的所有素数,然后用这些素数的倍数来筛掉[L,R]之间的所有合数。
假设现在的某一个素数为p,现在要找的值是大于等于L的第一个p的倍数 num

当L>p时,
如果L%p==0,则num=L;
如果p不能整除L,num=ceil(L/p)*p; ceil(L/p)=floor((L+p-1)/p)=(L+p-1)/p
当L<=p,则num = 2*p(因为p是素数,不能从p开始)

注意ceil的返回值类型为double,小心数值溢出!!!

AC代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
bool isPrime[50000+100];
int cnt;
int Prime[10000+10];
//欧拉筛法 
void Euler_Prime(int n)
{
	for(int i=1;i<=n;i++)
		isPrime[i] = 1;
	for(int i=2;i<=n;i++)
	{
		if(isPrime[i]==1)
			Prime[++cnt] = i;
		for(int j=1;j<=cnt&&i*Prime[j]<=n;j++) //枚举所有的素数 
			{
				isPrime[i*Prime[j]] = 0;
				if(i%Prime[j]==0) break;	
			}	
	} 
}
bool vis[1000000+10];
int main()
{ 
	ll L,R;
	cin>>L>>R;
	Euler_Prime(sqrt(R)) ;
	for(ll i=1;i<=cnt;i++)
	{
		ll p = Prime[i]; //取出的 某一个素数
      //(ll)ceil((double)L/(double)p)  要对 L/p 进行向上取整获取p的倍数,如果素数p大于L,则从2*p开始(不是p,因为p是素数,不能被筛掉) 
		for(ll j = max(2,(L+p-1)/p )*p ; j<=R&&j>=2;j+=p )//步长为p,不断寻找p的倍数
			vis[j-L+1] = 1; //vis数组的范围为[1,R-L+1]  数组下标平移,防止越界
	}
	int ans=0;
	for(ll i=1;i<=R-L+1;i++)
		if(vis[i]==0) ans++;
	cout<<ans;
}
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值