题解 P1835 【素数密度_NOI导刊2011提高(04)】

· · 题解

巨简单易懂的题解[连我都看得懂还有谁看不懂]

首先来看下数据范围[L,R] (L≤R≤2147483647,R-L≤1000000)

极值最大有20亿,因此O(N)的暴力筛会瞬间爆炸

再仔细看看题

题目要我们筛出L-R范围内的素数,那么我们只要将这个区间中的合数踢出去不就结束了吗w

说起判断合数,我就想到了美猴王合数的一个性质:可以分解为两个不为1且不等于本身的因子相乘 即 n=a*b(n为合数).下证之:

有了思路之后这道题就很简单了,大概流程如下

  1. 筛出1-50000中的所有质数,并且对合数打上标记.
  2. 在L-R的范围呢用我们已求出的质数筛出其中的合数(设p为质数,则i*p一定不为质数),并对其打上标记
  3. 遍历L-R,没有打标记的元素即为我们所求的素数

代码如下

/*Coded By Lxhao*/
/*Full Of Stars*/
#include <bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
const int maxn=1e6+1;
ll l,r;
int prime[maxn],cnt,ans;
bool vis[maxn];
inline void Gprime()//线性筛素数,预处理根号R内的素数,这里不赘述了,dalao们肯定都会qwq
{
    for(re int i=2;i<=50000;++i)
    {
        if(!vis[i])prime[++cnt]=i;
        for(re int j=1;i*prime[j]<=50000;j++)
        {
            vis[i*prime[j]]=1;//标记合数
            if(i%prime[j]==0) break;
        }
    }
}//50000的范围很小即使不用线性筛用根号N的筛法也能过
int main()
{
    ios::sync_with_stdio(false);//读入加速(在freopen下不可用!!!)
    cin>>l>>r;
    l=l==1?2:l;//特判L=1的情况
    Gprime();//筛出根号R内的所有质数以及剩下的合数
    memset(vis,0,sizeof(vis));//懒得多开一个数组,沿用函数中的数组时记得清空
    for(re int i=1;i<=cnt;++i)//枚举已经筛出来的质数
    {
        ll p=prime[i],start=(l+p-1)/p*p>2*p?(l+p-1)/p*p:2*p;//我们从大于L的最小的能被p整除的数开始,(l+p-1)就等于ceil(l+p-1),因为有可能会在接下来筛的过程中把自己也一起筛掉,所以在此特判一下
        for(re ll int j=start;j<=r;j+=p)vis[j-l+1]=1;//因为如果从j开始标记的话可能会爆vis的空间,所以我们这里从1开始标记合数,原理在上面已经叙述过了
    }
    for(re int i=1;i<=r-l+1;++i)if(!vis[i])ans++;//r-l+1即为区间长度,遍历区间找没有被标记的元素并累加答案
    cout<<ans;
}

算法的速度还是比较优秀的,不吸氧和吸氧都是65ms的样子

谢谢观看qwq