给定一个范围n,有q个询问,每次输出第k小的素数
输入格式
第一行包含两个正整数n, q, 分别表示查询的范围和查询的个数
接下来q行每行一个正整数k, 表示查询第k小的素数
输出格式
输出q行, 每行一个正整数表示答案
输入样例
100 3
1
2
3
输出样例
2
3
5
数据范围
对于100%的数据,n=10^8, 1<=q<=10^6, 保证查询的素数不大于n
讲解: 从小到大枚举每个数, 如果当前数没划掉, 必定是质数, 记录该质数;枚举当前质数的倍数, 必定是合数, 划掉合数
using ll=long long;
const int N=100000010
int vis[N];
int prim[N];
int cnt;
void Eratosthenes(int n){
for(LL i=2;i<=n;i++){
if(!vis[i]){
prim[++cnt]=i;
for(LL j=i*i;j<=n;j+=i){
vis[j]=1;
}
}
}
}
补充:n=20
i=2: p{2}; v{4,6,8,10,12,14,16,18,20};
i=3: p{3}; v{9,12,15,18}
i=4:
i=5: p{5}
i=6:
i=7: p{7}
i=8,9,10'
i=11: p{11}
i=12:
i=13: p{13}
i=14,15,16:
i=17: p{17}
i=18:
i=19: p{19}
时间复杂度:O(nloglogn)
为了进一步优化会重复划掉合数的情况, 我们引入欧拉筛
欧拉筛
从小到大枚举枚举每个数
1. 如果当前数没划掉, 必定是质数, 记录该质数
2. 枚举已记录的质数(如果合数已越界则中断)
(1) 合数未越界,则划掉合数
(2) 条件 i%p==0, 保证合数只被最小质因数划掉
若i是质数, 则最多枚举到自身中断
若i是合数, 则最多枚举到自身的最小质数中断
const int N=100000010;
using ll=long long;
int vis[N];
int prim[N]
int cnt;
void get_prim(int n){
for(int i=2;i<=n;i++){
if(!vis[i]) prim[cnt++]=i;
for(int j=1;1ll*i*prim[j]<=n;j++){
vis[i*prim[j]]=1;
if(i%prim[j]==0) break;
}
}
}
补充:
n=30
i=2: p{2}; v{4}; (2%2) break
i=3: p{3}; v{6,9}; (3%3) break;
i=4: p{5}; v{10,15,25}; (5%5) break;
i=7: p{7}; v{14,21}; (35,30)
i=8: v{16}; (8%2) break;
i=9: v{18,27}; (9%3) break;
i=10: v{20}; (10%2) break;
i=11: p{11};v{22}; (33>30)
i=12: v{24}; (12%2) break;
.......
i=16: (32>30)
时间复杂度:O(n)
每个质数只被记录一次, 每个合数只被划掉一次
所以执行次数为O(n)
旧题新发:
An表示1,2...n的最小公倍数, 求L...R的中不同数的个数
1=<L<=R<=10^14
R-L<=10^7
区间筛素数 -> 求[L,R]之间所有的素数
第一步: 利用欧拉筛或者埃氏筛求2-sqrt(R) 之间的素数
第二步: 利用求的素数, 划掉L,R之间所有的非素数
素数的倍数自然就是非素数
根据上一篇文章, 我们可以得出结论
统计区间(L,R]内的素数数量cnt1。
统计区间(L,R]内的质数高次幂数量 cnt2。
最终答案=1 + cnt1 + cnt2。统计cnt1使用区间筛法, 代码中先使用埃氏筛统计2-sqrt(R)中的所有素数
先从最小素数2开始, 2的倍数都是非素数, vis[i]=1
接着从3开始, 3^2=9, 后面顺次+3, 从平方开始是一个小优化, 因为6之前已经被2划过了
然后是5...
int n=sqrt(r);
int prim[N]; int cnt=0;
int vis[N]
for(int i=2;i<=n;i++){
if(!vis[i]){
prim[cnt++]=i;
for(ll j=i*i; i<=n;j+=i){
vis[j]=1;
}
}
}
接着进行对比, prim数组是从1开始进行存储素数的, is_prime[i]=true表示没有被划掉, 说明还是素数, 数组大小为区间大小 r-l+1;
for(int i=1;i<=cnt;i++){
int x=prim[i];
求x在[L,R]中的最小倍数
int st=max(x*x,(l+x-1)/x*x);
if(st<=r){
for(ll y=st; y<=r; y+=x){
is_prim[y-l]=false;
}
}
}
然后统计is_prim数组中素数的个数
cout(is_prim.begin(),is_prim_end(),true);
接着统计区间(L,R]内的质数高次幂数量 cnt2
ll tot_cnt=0;
统计prim数组中质数的高次方
for(int i=1; i<=cnt;i++){ 遍历前cnt个质数
int pr=prim[i]; 获取第i个质数
ll x=(ll)pr*pr; 从该质数的平方开始检查(因为要找的是至少是平方的幂次)
while(x<=r){ 只要当前值不超过右边界就继续
if(x>=l) tot_cnt++; 如果当前值大于左边界,就计数
if(x>r/pr) break; 检查下一次乘法是否会超过r,如果是就跳出循环
x*=pr; 计算下一个幂次
}
}
最后输出1 + prime_cnt + tot_cnt
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 100000010;
int vis[N];
int prim[N];
int cnt;
void Eratosthenes(int n) {
for (ll i = 2; i <= n; i++) {
if (!vis[i]) {
prim[++cnt] = i;
for (ll j = i * i; j <= n; j += i) {
vis[j] = 1;
}
}
}
}
ll count_primes(ll l, ll r) {
ll a = sqrt(r);
Eratosthenes(a);
ll s = r - l + 1;
vector<int> is_prime(s, true);
if (l == 1) is_prime[0] = false;
for (int i = 1; i <= cnt; i++) {
ll x = prim[i];
ll st = max(x * x, ((l + x - 1) / x) * x);
for (ll y = st; y <= r; y += x) {
is_prime[y - l] = false;
}
}
return count(is_prime.begin(), is_prime.end(), true);
}
void solve() {
ll l, r;
cin >> l >> r;
ll prime_cnt = count_primes(l + 1, r);
cnt = 0;
ll a = sqrt(r);
Eratosthenes(a);
ll tot_cnt = 0;
for (int i = 1; i <= cnt; i++) {
int pr = prim[i];
ll x = (ll)pr * pr;
while (x <= r) {
if (x > l) ++tot_cnt;
if (x > r / pr) break;
x *= pr;
}
}
cout << (1 + prime_cnt + tot_cnt) << endl;
}
int main() {
solve();
return 0;
}
感谢大家的点赞和关注,你们的支持是我创作的动力!