洛谷P3383(线性筛素数)题解

0.题目

题目传送门

1.暴力解法

暴力筛取1~n之间的素数,然后O(q)输出。时间复杂度为O(nsqrtn),面对1<=108的数据,显然会超时。

#include <bits/stdc++.h>
using namespace std;
int n, q, pr[5000005] = {0, 2}, cnt = 1;
bool check(int x) {
	if(x%2==0) return false;
	for(int i = 3;i*i <= x;i += 2)
		if(x%i == 0) return false;
	return true;
}
int main() {
    scanf("%d%d", &n, &q);
	for(int i = 3;i <= n;++i) 
		if(check(i)) pr[++cnt] = i;
	for(int i = 1, x;i <= q;++i) {
		scanf("%d", &x);
		printf("%d\n", pr[x]);
	}
    return 0;
}

在这里插入图片描述
所以,这种暴力不可行!

2.正解

在介绍正解之前,请你先了解一下埃拉托色尼筛法

2.1 核心算法

以1到100举例,要求出1到100之间的所有素数,就可以这么做。
第一步:保留2,并把所有2的倍数删掉
第二步:保留3,并把所有3的倍数删掉
第三步:保留5,并把所有5的倍数删掉
第四步:保留7,并把所有7的倍数删掉
此时11的平方已经是121,大于100了,算法结束。

2.2 算法的实现

我们首先需要一个超级大的vis数组(108+1)来标记每一个数是否被筛掉。
还需要一个超级大的pr数组(107+1)来存放每一个质数。(呜呜呜,我还能不能AC了)

2.3 上代码

根据我的介绍,各位大佬们也对这个算法有了一定的了解。下面我就直接把代码贴上来了。(维护洛谷、CSDN社区和平,请勿抄袭)

void solve(int n) {
	for(int i = 2;i <= n;++i) {
		if(!vis[i]) pr[++cnt] = i, vis[i] = true;
		for(int j = i*2;j <= n;j += i) vis[j] = true;
	}
}

2.4 总体代码

核心的函数搞定之后,剩下的就是输入输出了。

#include <bits/stdc++.h>
using namespace std;
int n, q, cnt, pr[10000001];
bool vis[100000001];
void solve(int n) {
	for(int i = 2;i <= n;++i) {
		if(!vis[i]) pr[++cnt] = i, vis[i] = true;
		for(int j = i*2;j <= n;j += i) vis[j] = true;
	}
}
int main() {
    scanf("%d%d", &n, &q);
	solve(n);
	for(int i = 1, x;i <= q;++i) {
		scanf("%d", &x);
		printf("%d\n", pr[x]);
	}
    return 0;
}

相信大家都能写出来吧。
提交试试:
还是超时
看来得需要进一步的优化了!

3. 终极优化

3.1 压缩时间复杂度

这个代码虽然运行时间更快,但本质上时间复杂度还是O(nsqrtn)。
现在我们要把这个时间复杂度降到O(n)。

3.2 新的算法

不知道你有没有发现:筛到一个数的时候只要这个数的平方之前的数都筛出来就可以了,至于筛再大的数,那纯粹是多管闲事。
所以算法就出来了:

void solve(int n) {
	for(int i = 2; i <= n; ++i){
		if(!vis[i]) pr[++cnt] = i;	
		for(int j = 1; j <= cnt && i*pr[j] <= n; ++j) {
			vis[i*pr[j]] = true;
			if(i % pr[j] == 0) break;
		}
	}
}

这样时间复杂度就成功降到了O(n),提交之后:
(如果这次我拿不到100分,我当场就把这个电脑屏幕吃掉)
在这里插入图片描述
总算是AC了(不要吃屏幕了)

4.总结

这道题考察了埃拉托色尼筛法,和优化算法的能力,关注我,带你解决更多难题!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

庄荣涛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值