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