题目描述
本题已更新,从判断素数改为了查询第 k 小的素数。
给定一个范围 n,有 q 个询问,每次输出第 k 小的素数。
提示:本题输入输出、运算数据量较大。
- 对于 C++ 语言,如果你使用
cin
来输入输出,建议使用std::ios::sync_with_stdio(0)
来加速,同时使用'\n'
换行输出。 - 对于 Java 语言,使用线性筛并且优化输入输出,也可以在规定时限内通过本题,但是时限可能较紧张。
- 对于 Python 语言,语言性能差异较大,需要使用到
numpy
库的数组以替代列表,且使用埃氏筛法,依然可以在合适的时间和内存消耗下通过本题。
这个在MYOJ有bug,就在洛谷交了
输入
第一行包含两个正整数 n,q,分别表示查询的范围和查询的个数。
接下来 q 行每行一个正整数 k,表示查询第 k 小的素数。
对于 100% 的数据,n=108,1≤q≤106,保证查询的素数不大于 n。
输出
输出 q 行,每行一个正整数表示答案。
样例输入输出
输入:
100 5
1
2
3
4
5
输出:
2
3
5
7
11
(100 5
1
25
26
27
1000
)
(2
97
0
0
0
)
思路一:
使用埃拉托斯特尼筛法,通过每次筛出质数的倍数来判定质数,时间复杂度O(log log n);同时使用空间换时间的方法节约时间。
STEP 1:声明标记数组,存储素数的prime,范围n,查询次数q,当前查询的个数k,计数pn
STEP 2:埃筛,先默认所有数是素数,然后把0、1去掉,开始主循环(专家提示部分开始)为了尽量节省时间,我么们需要先了解原理。
原理1.
-
合数的性质:任何合数(非素数)n都至少有一个质因数小于或等于√n
-
反证法证明:假设n是合数且所有质因数都大于√n,那么这些质因数的乘积将大于(√n)^2 = n,这与n的因数分解矛盾
结论1.
-
充分性:当用所有≤√n的素数筛选后,所有合数都已被标记
-
效率优化:将循环次数从n减少到√n,大幅降低时间复杂度
-
示例:筛选n=100时,只需检查2-10的素数(因为√100=10)
原理2.
-
避免重复标记:任何k*i(k<i)的倍数已经被更小的素数k筛选过
-
乘法对称性:i×k和k×i是相同的合数,k<i时已被处理
结论2.
-
最小未标记合数:对于素数i,i²是第一个未被之前素数筛选掉的i的倍数
-
效率优化:跳过已处理区域,减少不必要的操作
-
示例:
-
当i=5时,5×2=10(已被2筛选),5×3=15(已被3筛选),5×4=20(已被2筛选)
-
所以直接从5×5=25开始标记
-
综上所述,外循环从2到i*i,内循环从i*i到n
接着讲:如果i为素数,进内循环,标记所有的倍数为素数。操作完后就只剩下素数,接下来数组填充到质数数组中
STEP 3:读写优化,输入,筛数(在询问前就调用,不浪费时间),处理每个查询并输出
#include<bits/stdc++.h>
using namespace std;
bool isPrime[100000005];
int prime[10000005],n,q,k,pn;
void seive(int n)
{
memset(isPrime,1,sizeof(isPrime));
isPrime[0]=isPrime[1]=false;
for(int i=2;i*i<=n;i++)
{
if(isPrime[i])
{
for(int j=i*i;j<=n;j+=i)
{
isPrime[j]=false;
}
}
}
for(int i=2;i<=n;i++)
{
if(isPrime[i])
{
prime[++pn]=i;
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
seive(n);
while(q--)
{
cin>>k;
cout<<prime[k]<<"\n";
}
return 0;
}
思路二:
刚刚的埃筛法的缺点是会重复去除质数,浪费时间。为缩短时间,可使用线性筛法(欧拉筛法)时间复杂度O(n),他让每个合数只被它的最小质因子筛掉,这样每个合数只会被标记一次,从而保证线性的时间复杂度。
STEP 1:一样的定义
STEP 2:线性筛,初始假设都是素数,0,1,不是素数,遍历所有数,如果i是质数,将i存入质数数组,用已找到的质数筛选合数,标记i*prime[j]为合数,加上关键优化:保证每个合数只被它的最小质因子筛掉(是倍数跳出)
STEP 3:不变,函数名改一下
#include<bits/stdc++.h>
using namespace std;
bool isPrime[100000005];
int prime[10000005],n,q,k,pn;
void linearSeive(int n)
{
memset(isPrime,1,sizeof(isPrime));
isPrime[1]=false;
for(int i=2;i<=n;i++)
{
if(isPrime[i])
{
prime[++pn]=i;
}
for(int j=1;j<=pn&&i*prime[j]<=n;j++)
{
isPrime[i*prime[j]]=false;
if(i%prime[j]==0)
{
break;
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
linearSeive(n);
while(q--)
{
cin>>k;
cout<<prime[k]<<'\n';
}
return 0;
}
速度对比
埃筛:
线性筛: