质数差列 题解
题目id:20315
题目描述
驰骋宇宙的鱼大大找到了一个古遗迹,稍作研究后发现这是一个来着远古的质数星球文明遗迹,这个文明的特点是所有事物都和质数息息相关。于是,鱼大大赶紧列出了一堆的质数,以方便自己的研究。
这天鱼大大找到了质数星球文明的一个遗迹仓库大门,正准备破解密码的同时,助手羊大大找到了一段提示:此仓库密码为质数差列的和。
好在经过一段时间的研究鱼大大已经知道了质数差列为何物:已知长度为n的质数数列。对于一个长度为\(n\)的质数差列,其第一项\(a_1\)为第n个质数,之后的每一项\(a_i\)与前一项\(a_{i-1}\)的差为质数数列中的倒数第\(i\)项。
现给出质数差列的长度,问最后破解的密码为何?(即质数差列的和)
解题思路
由于题目中其第一项\(a_1\)为第n个质数这一句,再结合\(n\leq 10000\)的条件,枚举显然会浪费很多时间。
所以,我们考虑使用素数筛算法解决此题。
目前三种主流的筛法都可以通过本题:\(O(n\sqrt{n})\)的朴素筛、\(O(n\log\log n)\)埃拉托色尼筛法以及\(O(n)\)的欧拉筛(又称线性筛)。
虽然都可通过,但我还是推荐大家使用欧拉筛(因为快)。
欧拉筛的原理:
我们从\(2\)开始,逐条对\(2\sim 104729\)进行遍历(\(104729\)为第\(10000\)个质数)。
假设我们当前遍历到的数为\(i\),如果发现\(i\pmod x=0(x\)为枚举的质数\()\),则我们把\(i\)标记为非质数,因为\(i\)可以表示成至少两个数的乘积,这不符合质数的定义(在大于\(1\)的自然数中,除了\(1\)和该数自身外,无法被其他自然数整除的数)
到这里,大家觉得可能与\(O(n\log\log n)\)的埃拉托色尼筛法差不多,但欧拉筛的时间复杂度为\(O(n)\),显然还要做进一步优化。
我们知道,\(12\)可以表示成\(2×6\)或\(3×4\),这就导致了\(12\)既会被\(2\)筛掉一遍,又会被\(3\)筛掉一遍,十分浪费时间。
那我们如何避免这个问题呢?
我们只需要在代码里加上这一句即可:
if(i%prime[j]==0)break;
其中prime[j]
为第\(j\)个质数。
这样就可以保证每个数只被筛掉1遍。
欧拉筛代码如下:
for(ll i=2;i<104730;++i)
{
if(!isprime[i])prime[++cnt]=i;
for(ll j=1;j<=cnt&&prime[j]*i<104730;++j)
{
isprime[prime[j]*i]=1;
if(i%prime[j]==0)break;
}
}
后面的求和大家都会吧,不在此过多赘述了。
AC Code
#include<bits/stdc++.h>
#define N 1000007
#define INF 2147483647
#define ll long long
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MIN(a,b) ((a)<(b)?(a):(b))
#define IOS ios::sync_with_stdio(0),cin.tie(nullptr),cout.tie(nullptr)
std::mt19937_64 rnd(time(nullptr));
using namespace std;
ll n,ip[N+5],p[N+5],cnt,sum,a;
int main()
{
IOS,cin>>n;
for(ll i=2;i<=N;++i)
{
if(!ip[i])p[++cnt]=i;
for(ll j=1;j<=cnt&&p[j]*i<=N;++j)
{
ip[p[j]*i]=1;
if(i%p[j]==0)break;
}
}
for(int i=n;i;--i)a+=p[i],sum+=a;
cout<<sum;
return 0;
}