第一个错误是 AudioDroid 指出全局变量 i 正在被两个函数更改,即 main 和 prime_numbers。这可以通过定义一个局部变量来解决,比如 k 并循环使用它:
int k;
for (k = 2; k <= sqrt(n); k++) {
if (n % k == 0) return 0;
}
第二个bug是你写的
if (j > sqrt(a[i]) && count_divisors != 0) count1++;
在内部 for 循环体中,因此它永远不会被执行。它必须从那里取出。修复这两个错误,你会得到你的 5 作为答案。
但是,如果您的 q 大到 10^6 或更大,您的这个解决方案将不是很有效,因为对于每个除数 d,您都在循环 [2; sqrt(d)] 而不是在查找对象中生成直到给定限制的所有素数,并在 O(1) 时间内检查特定除数是否为素数。
#include <memory>
#include <bitset>
#include <iostream>
#include <vector>
int const lim = 1000000;
bool is_future(int const num, std::unique_ptr<std::bitset<lim + 1>> const& is_prime) {
int div, cnt = 0;
for (div = 2; div * div <= num; ++div) {
if (num % div == 0) {
++cnt;
if (!is_prime->test(div)) {
return false;
}
if (div * div != num) {
++cnt;
if (!is_prime->test(num / div)) {
return false;
}
}
}
}
return cnt != 0;
}
int main() {
auto sieve = std::make_unique<std::bitset<lim + 1>>();
sieve->set();
sieve->set(0, false);
sieve->set(1, false);
int i, j;
for (i = 2; i * i <= lim; ++i) {
if (sieve->test(i)) {
for (j = i * i; j <= lim; j += i) {
sieve->set(j, false);
}
}
}
int q;
std::cin >> q;
std::vector<int> seq;
seq.reserve(q);
int k, num;
for (k = 0; k != q; ++k) {
std::cin >> num;
seq.emplace_back(num);
}
int ans = 0;
for (auto const& elem : seq) {
if (is_future(elem, sieve)) {
++ans;
}
}
std::cout << ans << '\n';
return 0;
}
根据输入中数字的最大值将 lim 常量设置为足够高。通过 unique_ptr 筛子在堆上分配位集。在 lim 的筛子上运行 Eratosthenes 算法的筛子。阅读 q。定义向量序列。使用 reserve 为 q 个元素分配内存,从而避免预分配。增长序列。用 0 初始化计数器 ans。遍历 seq 并为每个元素 elem 调用 is_future 和 elem 和 sieve。 is_future 使用 is_prime (sieve) 作为查找对象来检查除数 div 或 (num/div) 在 O(1) 时间内是否为素数。计数器 cnt 统计 num 的除数在范围 [2; sqrt(数字)]。请注意,我不使用 std::sqrt 因为它很昂贵,如果被调用数千次。如果 cnt 为 0,则该数字在指定范围内没有除数,因此它不是未来,否则它是。如果 is_future 返回 true 增量 ans。输出答案。
@Trương Quang Vinh
seq.reserve(q);
假设你有一个空的向量序列。它的初始容量为 0,这意味着它为 0 个整数分配了一块内存。如果你开始增长这个向量,假设你想读取 10^6 个整数并将它们存储在其中,它必须执行以下操作:
分配更大的内存块
将旧块中的整数复制到新块中
调用位于旧块上的整数的析构函数
释放旧块上的整数使用的内存并将其返回给系统
我用 10^6 个整数做了一个测试,在微软的实现下,vector seq 的容量是这样变化的:
0 -> 1 -> 2 -> 3 -> 4 -> 6 -> 9 -> 13 -> 19 -> 28 -> 42 -> 63 -> 94 -> 141 -> 211 -> 316 - > 474 -> 711 -> 1066 -> 1599 -> 2398 -> 3597 -> 5395 -> 8092 -> 12138 -> 18207 -> 27310 -> 40965 -> 61447 -> 92170 -> 138255 -> 207382 -> 3110 -> 466609 -> 699913 -> 1049869
在向量 seq 改变其容量的每个步骤中,上述 4 个操作都会发生,这当然需要一些时间。在您的情况下,您知道要在向量中存储 q 个整数,并且使用成员函数 reserve,您可以直接分配一个足够大的内存块来保存它们,从而避免上述 4 个操作。
seq.emplace_back(num);
成员函数 emplace_back 直接根据给定的参数构造一个元素,而不是像 push_back 那样从根据给定参数创建的副本构造一个元素,因此更有效。
is_prime->test(num / div)
is_prime 是一个 unique_ptr,它指向位于堆上的 std::bitset 类型的对象。 bitset 类模仿数字的位。在您的情况下,您将在此对象上运行 Eratosthenes 算法的筛子。假设您要生成范围 [2; 中的所有素数; 100]。您的 bitset 对象最初看起来像这样:
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100
表示数字 0 和 1 的最后两位设置为 0,因为 0 和 1 不是素数。当算法完成时,您的 bitset 对象将如下所示:
00010000000100000100010000010100010000010100000100000100010100010000010100000100010100010100010101100
通过带有 operator-> 的指针 is_prime 调用的函数 test 是 (*is_prime) 的缩写形式。test 将测试位假设 x 是 1 还是 0。如果是 1,则该位表示的数字x 是素数,否则不是。
std::unique_ptr<std::bitset<lim + 1>> const& is_prime
这是作为对 const 的引用传递给函数 is_future 的对象 sieve,这意味着对 is_prime 执行的操作是在 sieve 上完成的,并且承诺不会更改它,因为 is_prime 是对 const 的引用。如果我不在这一行写 auto
auto sieve = std::make_unique<std::bitset<lim + 1>>();
我必须写
std::unique_ptr<std::bitset<lim + 1>> sieve = std::make_unique<std::bitset<lim + 1>>();
这是 main 中缺少的类型 unique_ptr 的来源。