如何使用Eratosthenes算法的Sieve改进我的素数程序?

时间:2019-01-07 10:42:40

标签: c++ primes

我的程序从该表达式打印所有素数:

((1 + sin(0.1*i))*k) + 1, i = 1, 2, ..., N.

  

输入格式:

     

不超过100个示例。每个示例在同一行上都有2个正整数。

     

输出格式:

     

将每个数字打印在单独的行上。

     

样本输入:

     

4 10

     

500100

     

样本输出:

     

5

     

17

但是我的算法不够有效。我该如何添加Eratosthenes筛网,这样它才能足够有效地避免打印“由于超时而终止”。

#include <iostream>
#include <cmath>
using namespace std;

int main() {
long long k, n;
int j;
    while (cin >> k >> n) {
    if (n>1000 && k>1000000000000000000) continue;

    int count = 0;
    for (int i = 1; i <= n; i++) {
        int res = ((1 + sin(0.1*i)) * k) + 1;
        for (j = 2; j < res; j++) {
            if (res % j == 0) break;
        }
        if (j == res) count++;
    }
    cout << count << endl;

}
system("pause");

3 个答案:

答案 0 :(得分:1)

编辑:我添加了第三个方法来提高效率
EDIT2:添加了一个解释,为什么Sieve不应该是解和某些三角关系。此外,我还添加了有关该问题历史的注释

您的问题不是要计算给定范围内的所有素数,而是仅计算由函数生成的素数。

因此,我不认为Eratosthenes筛是解决此特定问题的方法,原因如下:n总是很小,而k可能很大。如果k非常大,那么Sieve算法将不得不生成大量的质数,以便最终将其用于少数候选对象。

您可以通过三种方式提高程序的效率:

  • 避免每次都计算sin(.)。例如,您可以使用三角关系。此外,第一次计算这些值时,请将它们存储在数组中并重新使用这些值。 sin()的计算非常耗时
  • 在检查数字是否为质数的测试中,将搜索限制为sqrt(res)。此外,考虑仅使用奇数j加上2
  • 进行测试
  • 如果候选人res与前一个候选人相同,请避免重做测验

一些三角学
如果c = cos(0.1)和s = sin(0.1),则可以使用以下关系:

  • sin (0.1(i+1)) = s*cos (0.1*i) + c*sin(0.1*i))
  • cos (0.1(i+1)) = c*cos (0.1*i) - s*sin(0.1*i))

如果n大,则必须定期通过函数重新计算sin(),以避免过多的舍入误差计算。但这不是事实,因为n总是很小。

但是,正如我提到的,最好只使用“记忆”技巧,然后检查它是否足够。

有关此问题的历史记录以及此答案的原因的注释:

最近,该站点收到了几个问题“如何改进我的程序,计算由该k*sin()函数生成的质数的数量...”据我所知,在Sieve是解决方案的原因,并且在先前的类似(但略有不同)问题中得到了解释。现在,相同的问题以稍有不同的形式再次出现“我如何在该程序中插入Sieve算法...(再次使用k * sin())”。然后我意识到筛网不是解决方案。我对问题的理解也犯了同样的错误,所以这并不是对以前的评论的批评。但是,我认为是时候提出一个新的解决方案了,即使它与新问题并不完全吻合

答案 1 :(得分:1)

使用简单的Wheel factorization时,可以获得非常不错的代码加速。 2阶的车轮分解利用了以下事实:对于自然的 n 6n + 1 6n + 5 >。这意味着您仅需对6个数字进行2个除法。甚至更进一步,所有大于5的素数都可以写为 30n + m ,其中 m {1,7,11,13,17,19, 23,29} 。 (每30个数字8个格)。

使用此简单原理,您可以编写以下函数来测试素数(车轮{2,3}):

bool isPrime(long long num) {
  if (num == 1)     return false;   // 1 is not prime
  if (num  < 4)     return true;    // 2 and 3 are prime
  if (num % 2 == 0) return false;   // divisible by 2
  if (num % 3 == 0) return false;   // divisible by 3
  int w = 5;
  while (w*w <= num) {
      if(num % w     == 0) return false; // not prime
      if(num % (w+2) == 0) return false; // not prime
      w += 6;
  }
  return true;                     // must be prime
}

您可以使以上内容适用于滚轮{2,3,5}。此功能可在主程序中用作:

int main() {
  long long k, n;

  while (cin >> k >> n) {
    if (n>1000 && k>1000000000000000000) continue;
    int count = 0;
    for (int i = 1; i <= n; i++) {
      long long res = ((1 + sin(0.1*i)) * k) + 1;
      if (isPrime(res)) { count++; }
    }
    cout << count << endl;
  }
  return 0;
}

一个简单的时间就为我提供了原始代码(g++ prime.cpp

 % time echo "6000 100000000" | ./a.out 
 12999811
 echo "6000 100000000"  0.00s user 0.00s system 48% cpu 0.002 total
 ./a.out  209.66s user 0.00s system 99% cpu 3:29.70 total

优化版本为我提供

% time echo "6000 100000000" | ./a.out                                                                                                                                                                                                        
12999811
echo "6000 100000000"  0.00s user 0.00s system 51% cpu 0.002 total
./a.out  10.12s user 0.00s system 99% cpu 10.124 total

可以进行其他改进,但可能会产生较小的影响:

  1. sin(0.1*i)的正弦表i从0预先计算为1000。这样可以避免一遍又一遍地重新计算这些正弦。但是,这影响不大,因为大多数时间都浪费在了测试上。
  2. 检查res(i) == res(i+1)是否几乎没有影响,因为取决于nk,最连续的res不相等。
  3. 使用查找表,可能会更方便,这确实会产生影响。

原始答案:

我的建议如下:

  1. sin(0.1*i)的正弦i从0预先计算为1000。这样可以避免一遍又一遍地重新计算这些正弦。另外,要聪明一点(请参阅第3点)
  2. 找到res的最大可能值res_max=(2*k)+1
  3. 使用Sieve of Eratosthenes查找res_max的所有素数。此外,请注意,对于自然的 n ,所有大于3的素数都可以写为 6n + 1 6n + 5 。甚至更进一步,所有大于5的素数都可以写为 30n + m ,其中 m {1,7,11,13,17,19, 23,29} 。这就是所谓的Wheel factorization。因此,请勿打扰检查其他任何数字。 (更多信息here

  4. 有一个查找表,指出数字是否为质数。

  5. 遍历查找表。

答案 2 :(得分:1)

仅通过在审判部门做得更好,您就可以将速度提高10倍。您正在测试从2到res的所有整数,而不是将2视为特殊情况,而是测试从3到res的平方根的奇数:

// k <= 10^3, n <= 10^9

int main() {
    unsigned k;
    unsigned long long n;

    while (cin >> k >> n) {

        unsigned count = 0;

        for (unsigned long long i = 1; i <= n; i++) {
            unsigned long long j, res = (1 + sin(0.1 * i)) * k + 1;

            bool is_prime = true;

            if (res <= 2 || res % 2 == 0) {
                is_prime = (res == 2);
            } else {
                for (j = 3; j * j <= res; j += 2) {
                    if (res % j == 0) {
                        is_prime = false;
                        break;
                    }
                }
            }

            if (is_prime) {
                count++;
            }
        }

    cout << count << endl;
    }
}

尽管k = 500和n = 500000000仍然需要40秒钟左右。