洛谷题单指南-数学基础问题-P1835 素数密度

原题链接:https://www.luogu.com.cn/problem/P1835

题意解读:要计算L-R范围内素数的个数。

解题思路:

直接对L~R的每个数判断素数肯定不可取,因为L、R的范围较大。

既然要计算素数的个数,那么可以把其中的合数标记出来即可。

如何标记合数?

可以借助于筛素数的算法思想,枚举每一个素数,然后把素数的2/3/4....n倍,且乘以倍数后在L~R范围内的数标记成合数,最后计算素数的个数即可。

问题在于,枚举每一个素数,这个最大的素数到多少合适?会不会超时?

如果合数最大是x,那么素数最多只要枚举到sqrt(x),就可以把所有合数都标记到。

因此,我们要枚举的素数最大在10^5内即可,放大一点,我们可以先筛出10^6范围内的所有素数,再枚举倍数,标记L~R范围内的合数。

枚举倍数从多少开始,到多少结束呢?

给定一个素数primes[i],倍数j起始点肯定是j * primes[i]接近l,倍数j的结束点肯定是j * primes[i]解决r,这样枚举次数最少

因此起点start = l / primes[i],终点end = r / primes[i]即可。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 5;

int primes[N], cnt;
bool flag[N];
int l, r, c;
int h[N]; //标记出l~r之间的合数

int main()
{
    cin >> l >> r;
    if(l == 1) l++; //排除1的影响

    //筛2~1000000质数
    for(int i = 2; i <= 1000000; i++)
    {
        if(!flag[i])
        {
            primes[++cnt] = i;
            for(int j = i + i; j <= 1000000; j += i)
            {
                flag[j] = true;
            }
        }
    }

    for(int i = 1; i <= cnt; i++) //枚举每一个素数,再枚举倍数,使得素数*倍数在l~r范围内,则标记为合数
    {
        int start = l / primes[i]; //筛出l-r范围内的合数,也就是素数的倍数从l/primes[i]开始
        int end = r / primes[i]; //倍数到r/primes[i]截止
        if(start <= 1) start = 2; //起始至少从2倍开始
        if(end <= 1) break; //如果截止倍数不到2倍,则不可能筛出合数,后面的素数更大,end更小,提前退出
        for(int j = start; j <= end; j++)
        { 
            if(j * primes[i] >= l)
            {
                h[j * primes[i] - l] = 1; //标记l~r之间的合数,-l防止溢出
            } 
        }
    }
    
    int ans = r - l + 1;
    for(int i = 0; i <= r - l; i++)
    {
        if(h[i]) ans--;
    }
    cout << ans;

    return 0;
}

 

posted @ 2024-04-11 13:05  五月江城  阅读(51)  评论(0编辑  收藏  举报