基础算法合集-埃氏筛/欧拉筛

给定一个范围n,有q个询问,每次输出第k小的素数

输入格式

第一行包含两个正整数n, q, 分别表示查询的范围和查询的个数

接下来q行每行一个正整数k, 表示查询第k小的素数

输出格式

输出q行, 每行一个正整数表示答案

输入样例

100 3

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;
}

 感谢大家的点赞和关注,你们的支持是我创作的动力!

### 埃氏筛法与欧拉法的对比分析 #### 一、埃氏筛法的核心概念与实现 埃氏筛法(Eratosthenes Sieve)是一种经典的素数选方法,其核心思想是通过标记合数的方式逐步找到范围内的所有素数。具体而言,该算法会从小到大依次枚举每一个尚未被标记为合数的整数 \( p \),并将 \( p \) 的所有倍数标记为合数。 以下是埃氏筛法的具体实现: ```python def eratosthenes_sieve(n): is_prime = [True] * (n + 1) is_prime[0], is_prime[1] = False, False for i in range(2, int(n ** 0.5) + 1): # 遍历至 sqrt(n)[^2] if is_prime[i]: for j in range(i * i, n + 1, i): # 将 i 的倍数标记为非素数[^1] is_prime[j] = False primes = [i for i in range(2, n + 1) if is_prime[i]] return primes ``` 埃氏筛的时间复杂度为 \( O(n \log \log n) \),空间复杂度为 \( O(n) \)。然而,在实际运行过程中,某些合数可能会被多次标记为非素数,这导致了一定程度上的冗余操作。 --- #### 二、欧拉法的核心概念与实现 欧拉法(也称为线性),是在埃氏筛基础上的一种改进版本。它通过对每个合数仅由其最小质因数进行唯一一次除的操作,实现了时间复杂度的进一步优化——达到严格的线性复杂度 \( O(n) \)。 下面是欧拉法的具体实现: ```python def euler_sieve(n): is_prime = [True] * (n + 1) is_prime[0], is_prime[1] = False, False primes = [] for i in range(2, n + 1): if is_prime[i]: # 如果当前数未被标记,则是一个新的素数 primes.append(i) for prime in primes: if i * prime > n: # 超过上限则停止 break is_prime[i * prime] = False # 掉对应的合数 if i % prime == 0: # 当前数能被某个已知素数整除时退出循环[^3] break return primes ``` 相比埃氏筛欧拉的关键在于引入了一个额外条件 `if i % prime == 0` 来确保每个合数只会被其最小质因数除一次,从而避免了重复计算。 --- #### 三、两种算法的主要区别 | 特性 | 埃氏筛法 | 欧拉法 | |--------------------|-----------------------------------|-------------------------------| | 时间复杂度 | \( O(n \log \log n) \) | \( O(n) \) | | 是否存在重复除 | 存在 | 完全不存在 | | 实现难度 | 较低 | 稍高 | | 应用场景 | 对于较小规模数据集适用 | 更适合大规模数据处理 | 从上述表格可以看出,虽然两者都能有效解决素数选问题,但在性能上欧拉更优,尤其是在面对较大输入范围的情况下表现尤为突出。 --- ### 总结 综上所述,尽管埃氏筛法因其简洁性和直观性而广受欢迎,但它仍然存在着一定的效率瓶颈;相比之下,欧拉法则凭借其独特的机制成功克服了这一缺陷,成为更为高效的选择之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值