一、题目描述
二、算法分析说明与代码编写指导
事实上在此之前我一直使用自己写的埃氏筛法的模板来快速打出前若干个质数:
unsigned prime[6542] = { 2,3 }, _PrimeTy, MaxPrime, * prime_end = prime + sizeof(prime) / sizeof(prime[0]);
inline void gen_prime() {
decltype(_PrimeTy) a = 4, t; bool flag = true;
for (auto i = prime + 2; i != prime_end;) {
t = sqrt(a), flag = true;
for (auto j = prime; *j <= t; ++j)if (a % *j == 0) { flag = false; break; }
if (flag) { *i = a, ++i; }
++a;
}
MaxPrime = *(prime_end - 1);
}
以及配套的判断质数的模板:
inline bool isprime(const decltype(_PrimeTy)& x) {
if (x <= MaxPrime)return binary_search(prime, prime_end, x);
else {
static decltype(_PrimeTy) t; t = min((decltype(_PrimeTy))sqrt(x), MaxPrime);
for (auto j = prime; *j <= t; ++j)if (x % *j == 0)return false;
return true;
}
}
在本题的数据更新之前,该方法写的代码(传送门)甚至比一些欧拉筛的代码表现出了更优秀的耗时:
从上图可见,在本题数据和要求更新之前,这种改进实现方法的埃氏筛法的平均总耗时约 200 ms,而当时排在首页的前几个题解的总耗时超过 700 ~ 800 ms 甚至超过了 1 s(当时提交了许多次,而且一些提交的代码略有不同,因为我在做微调以尽可能优化)。
但是今天在做 LightOJ 1259 时,我发现如果直接套这个模板会导致超时。即使在打表后通过 bitset 来 O(1) 判定 1 ~ 10^7 内的任意一个数是否为质数,该题的耗时也超过了 1900 ms,远远高于网上的其它题解。
然后我开始找一些题打算重新对模板进行测试,无意中发现 P3383 的数据和要求都变动了。
本题中 n = 10^8,查表得 10^8 以内的质数共有 5761455 个。如果直接套用上面给的埃氏筛的模板,打 10^8 以内的质数要接近 14 s(release 模式下)。所以,必须换用时间复杂度更低的方法。
三、AC 代码(总时间:4.42 s)
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<bitset>
#pragma warning(disable:4996)
using namespace std;
unsigned prime[5761455], _PrimeTy, MaxPrime, * prime_end = prime; bitset<100000001> notprime;
inline void gen_prime() {
decltype(_PrimeTy) n = notprime.size() - 1, m = n / 2, L; notprime[0] = notprime[1] = 1;
for (decltype(_PrimeTy) i = 2; i <= m; ++i) {
if (!notprime[i]) { *prime_end = i, ++prime_end; }
L = n / i;
for (auto j = prime; j != prime_end && *j <= L; ++j) {
notprime[i * *j] = true; if (i % *j == 0)break;
}
}
for (decltype(_PrimeTy) i = m + 1; i <= n; ++i)
if (!notprime[i]) { *prime_end = i, ++prime_end; }
MaxPrime = *(prime_end - 1);
}
unsigned* Prime = prime - 1, n, q, k;
int main() {
gen_prime();
scanf("%u%u", &n, &q); ++q;
while (--q) {
scanf("%u", &k);
printf("%u\n", Prime[k]);
}
return 0;
}