MYOJ_5775:(洛谷P3383)【模板】线性筛素数(质数筛模板)

题目描述

本题已更新,从判断素数改为了查询第 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;
}

速度对比

埃筛: 

线性筛:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值