质数
1.质数
若对于一个大于 \(1\) 的正整数 \(x\),有:
\(\forall1< y< x ,y\nmid x\)。
则称此数为质数(素数),否则此数为合数。
判定一个质数的方法之试除法:检查 \(2\sim \sqrt{n}\) 之间的所有数是否能整除 \(n\)。
bool check(int x) {
int cnt=sqrt(x);
for(int i=2;i<=cnt;++i) {
if(x%i==0) return false;
}
return true;
}
找 \(1\sim n\) 之间所有的质数:筛法。
暴力
对 \(1\sim n\) 的所有数判断是否是质数。
时间复杂度 \(O(n\sqrt{n})\)。
朴素筛法
扫描 \(1\sim n\),标记 \(n\) 以内所有的倍数,若当前没被标记则为质数。
调和级数知时间复杂度约为 \(O(n\log n)\)。
埃拉托斯特尼筛法(埃氏筛)。
考虑优化上述筛法,发现只要标记质数的倍数就行。
并且由于 \(1\sim n\) 的数只需扫描 \(1\sim \sqrt n\) 的数即可全部标记。
时间复杂度趋近于 \(O(n\log\log n)\)。
int vis[N],n;
vector<int>prime;
for(int i=2;i*i<=n;++i) {
if(vis[i]) continue;
for(int j=i*i;j<=n;j+=i) vis[j]=1;
}
for(int i=1;i<=n;++i) if(!vis[i]) prime.push_back(i);
线性筛法(欧拉筛)
再次考虑优化,发现会重复标记很恶心。
考虑如何仅标记一次,直接被最小质因子标记一次即可。
具体见注释。
int v[N],n;
vector<int>prime;
for(int i=2;i<=n;++i) {
if(!v[i]) v[i]=i,prime.push_back(i);//是质数。
for(int p:prime) {
if(p*i>n||v[i]<p) break;//超过范围或者有更小质因子。
v[p*i]=p;
}
}
找大于 \(x\) 的最小的质数/小于 \(x\) 的最大的质数:
筛出符合范围的所有质数,再二分查找。
但真的很优秀吗?若至算法一个!
素数密度:在 \(1\sim n\) 中约有 \(\frac{n}{\ln n}\) 个质数,即每 \(\ln n\) 个数中大约有一个质数。
根据性质,我们直接暴力向前/后扫,试除法判断扫到的数是不是质数。
时间复杂度为 \(O(\sqrt n \log n)\)。
区间筛法
\(Q:\) 给定 \(l,r\),求区间 \([l,r]\) 以内的所有质数。
\(l,r\le 1e9,r-l\le 1e6\).
暴力筛 \(r\) 以内所有质数不现实。
发现 \([l,r]\) 以内的所有数只需用埃氏筛标记 \(1\sim \sqrt r\) 中的质数即可将质数全部筛出。
所以用偏移量数组,按照普通埃氏筛即可。
复杂度 \(O(m\log\log m)\),其中 \(m=r-l\)。
void Eratosthenes(ll n){
for(ll i=2;i*i<=n;i++)
for(ll j=(l+i-1)/i*i;j<=n;j+=i)
if(j!=i)b[j-l]=0;
}
int main() {
scanf("%lld%lld",&l,&r);
Eratosthenes(r);
for(int i=l;i<=r;i++)
if(b[i-l]) ans++;
ans-=max(2-l,0);
printf("%lld\n",ans);
return 0;
}