洛谷題單指南-數學基礎問題-P1835 素數密度

江城伍月發表於2024-04-11

原題連結: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;
}

相關文章