写一个数字作为连续素数的总和

时间:2014-10-30 13:53:40

标签: algorithm primes

如何检查n是否可以划分为一系列连续素数的总和。

例如,12等于5+7,其中5和7是连续素数,但20等于3+17,其中3和17不连续。< / p>

请注意,不允许重复。

我的想法是查找并列出n以下的所有素数,然后使用2个循环对所有素数求和。前两个数字,第二个2个数字,第三个2个数字等然后前3个数字,第二个3个数字和到目前为止。但这需要大量的时间和记忆。

5 个答案:

答案 0 :(得分:2)

意识到连续的素数列表仅由两条信息定义,即起始和结束素数。你只需要找到这两个数字。

我假设您拥有所有素数,并在名为primes的数组中排序。在内存中保留三个变量:sum最初为2(最小素数),first_indexlast_index最初为0(数组primes中最小素数的索引)

现在你必须&#34;调整&#34;这两个指数,&#34;旅行&#34;循环中的数组:

如果sum == n则完成。你找到了素数序列。

如果sum < n,则通过添加下一个可用素数来放大列表。将last_index增加1,然后将sum增加新素数的值,即primes[last_index]。重复循环。但如果primes[last_index]大于n,那么就没有解决方案了,你必须完成。

如果sum > n,则通过从列表中删除最小的素数来减少列表。按该值减去sum,即primes[first_index],然后将first_index增加1。重复循环。

答案 1 :(得分:1)

我首先注意到,对于一对连续的素数求和,其中一个素数必须小于N / 2,而另一个素数必须大于N / 2。要使它们成为连续素数,它们必须是最接近N / 2的素数,一个较小而另一个较大。

如果您从一个素数表开始,您基本上会对N / 2进行二进制搜索。看看比这更大和更小的素数。将这些数字加在一起,看看它们是否与您的目标数字相加。如果他们不这样做,则它不能是两个连续素数的总和。

如果你不是以一个素数表开始,它的工作方式几乎相同 - 你仍然从N / 2开始并找到下一个更大的素数(我们称之为prime1)。然后你减去N-prime1以获得prime2的候选者。检查是否为素数,如果是,则搜索范围prime2 ... N / 2以寻找其他素数以查看其间是否存在素数。如果你的数字之间有一个素数是非连续素数的总和。如果该范围内没有其他素数,则它是连续素数的总和。

同样的基本思想适用于3个或更多素数的序列,除了(当然)你的搜索从N / 3开始(或者你想要求数的任何数量的素数)。

因此,对于总和为N的三个连续素数,三个中的2个必须是小于N / 3的第一个素数,并且第一个素数大于N / 3。所以,我们首先找到那些,然后计算N-(prime1 + prime2)。这使我们的第三个候选人。我们知道这三个数字总和为N.我们仍需要证明这第三个数字是素数。如果它是素数,我们需要验证它是否与其他两个连续。

举一个具体的例子,对于10,我们从3.333开始。下一个较小的素数是3,下一个较大的素数是5.那些加到8. 10-8 = 2. 2是素数,连续到3,所以我们发现连续三个素数加到10。

您还可以进行其他一些改进。最明显的是基于所有素数(除2之外)都是奇数的事实。因此(假设我们可以忽略2),偶数只能是偶数个素数的和,而奇数只能是奇数个素数的和。因此,给定123456789,我们立即知道它不可能是2(或4,6,8,10,......)连续素数的总和,因此唯一考虑的候选者是3,5 ,7,9,...素数。当然,相反的情况也是如此:比如说12345678,这个简单的事实就是它甚至可以让我们立即排除它可能是3个,5个,7个或9个连续素数之和的可能性;我们只需要考虑2,4,6,8,...素数的序列。只有当我们得到足够数量的素数时,我们才会违反这个基本规则,我们可以将2作为序列的一部分。

我没有通过数学计算确切地确定给定数字的数量,但我很确定它应该相当容易这是我们想要的东西无论如何都知道(因为它是给定数字寻找连续素数的上限)。如果我们使用M作为素数的数量,则限制应该大约 M <= sqrt(N),但这绝对只是近似值。

答案 2 :(得分:1)

素数必须是连续的这一事实允许在 n 方面非常有效地解决这个问题。让我假设我们之前已经计算了所有小于或等于 n 的素数。因此,我们可以轻松地计算 sum(i)作为第一个 i 素数的总和。

预先计算好这个函数,我们可以循环小于或等于 n 的素数,看看是否存在一个长度,使得从该素数开始,我们可以求和 n 。但请注意,对于固定的起始素数,求和序列是单调的,因此我们可以在长度上进行二进制搜索。

因此,让 k 是小于或等于 n 的素数。预计算总和的成本 O(k),并且循环的成本 O(klogk),主导成本。使用Prime number theorem,我们知道 k = O(n / logn),然后整个算法的成本 O(n / logn log(n / logn))=为O(n)

让我用C ++编写代码使其更清晰,希望没有错误:

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

typedef long long ll;

int main() {
  //Get the limit for the numbers
  int MAX_N;
  cin >> MAX_N;

  //Compute the primes less or equal than MAX_N
  vector<bool> is_prime(MAX_N + 1,  true);
  for (int i = 2; i*i <= MAX_N; ++i) {
    if (is_prime[i]) {
      for (int j = i*i; j <= MAX_N; j += i) is_prime[j] = false;
    }
  }
  vector<int> prime;
  for (int i = 2; i <= MAX_N; ++i) if (is_prime[i]) prime.push_back(i);

  //Compute the prefixed sums
  vector<ll> sum(prime.size() + 1, 0);
  for (int i = 0; i < prime.size(); ++i) sum[i + 1] = sum[i] + prime[i];

  //Get the number of queries
  int n_queries;  
  cin >> n_queries;
  for (int z = 1; z <= n_queries; ++z) {
    int n;
    cin >> n;

    //Solve the query
    bool found = false;
    for (int i = 0; i < prime.size() and prime[i] <= n and not found; ++i) {

      //Do binary search over the lenght of the sum:
      //For all x < ini, [i, x] sums <= n
      int ini = i, fin = int(prime.size()) - 1;
      while (ini <= fin) {
        int mid = (ini + fin)/2;
        int value = sum[mid + 1] - sum[i];
        if (value <= n) ini = mid + 1;
        else fin = mid - 1;
      }

      //Check the candidate of the binary search
      int candidate = ini - 1;
      if (candidate >= i and sum[candidate + 1] - sum[i] == n) {
        found = true;
        cout << n << " =";
        for (int j = i; j <= candidate; ++j) {
          cout << " ";
          if (j > i) cout << "+ ";
          cout << prime[j];
        }
        cout << endl;
      }
    }

    if (not found) cout << "No solution" << endl;
  }
}

示例输入:

1000
5
12
20
28
17
29

示例输出:

12 = 5 + 7
No solution
28 = 2 + 3 + 5 + 7 + 11
17 = 2 + 3 + 5 + 7
29 = 29

答案 3 :(得分:1)

Dialecticus's algorithm是解决此类问题的经典O(m) - 时间,O(1) - 空间方法(这里我用m来表示小于n的素数个数) )。它并不依赖于素数的任何神秘属性。 (有趣的是,对于素数的特定情况,AlexAlvarez's algorithm也是线性时间!)Dialecticus给出了清晰正确的描述,但似乎无法解释为什么它是正确的,所以我会在这里尝试这样做。我真的认为花些时间来理解这个特殊算法的正确性证明是有价值的:虽然我最终必须先阅读一些解释才能最终&#34;沉没在&#34;中,它是一个真实的&#34;啊哈!&#34;它的时刻! :)(此外,可以以相同的方式有效解决的问题非常多。)

此算法尝试的候选解可以表示为数字范围(i,j),其中i和j只是素数列表中第一个和最后一个素数的索引。该算法通过以两种不同方式排除(即不考虑)数字范围集来获得其效率。为了证明它总是给出正确的答案,我们需要证明它永远不会排除具有正确总和的唯一范围。为此,只需要证明它永远不会排除第一个(最左边)范围和正确的总和,这就是我们在这里所做的。

它适用的第一个规则是每当我们找到一个范围(i,j)和sum(i,j)&gt; n,我们排除所有具有k> 1的范围(i,k)。学家很容易理解为什么这是合理的:总和只能随着我们添加更多术语而变大,我们已经确定它已经太大了。

对线性时间复杂度至关重要的第二个棘手的规则是,每当我们将范围(i,j)的起点从i推进到i + 1,而不是&#34;再次开始&#34;从(i + 1,i + 1)开始,我们从(i + 1,j)开始 - 也就是说,我们避免考虑(i + 1,k)所有i + 1&lt; = k&lt;学家为什么这样做可以? (换问题的另一个方面是:无法做到这一点导致我们用正确的金额跳过某个范围?)

[编辑:下一段的原始版本掩饰了一个微妙之处:我们可能已经将任何上一步的范围终点提升为j。]

要看到它永远不会跳过有效范围,我们需要考虑范围(i,j-1)。对于提前当前范围的起点的算法,使得它从(i,j)变为(i + 1,j),它必须是该和(i,j)> N;正如我们将要看到的,为了进入一个程序状态,其中首先考虑范围(i,j),它必须是该总和(i,j-1)&lt; ñ。第二个主张是微妙的,因为有两种不同的方式来达到这样的程序状态:要么我们只增加了终点,意味着先前的范围是(i,j-1)并且发现这个范围太小(在这种情况下,我们期望的属性和(i,j-1)&lt; n显然成立);或者我们只是在考虑(i-1,j)并且发现它太大之后增加了起点(在这种情况下,该属性仍然存在并不明显)。

然而,我们所知道的是,无论上一步的终点是否从j-1增加到j,在当前步骤之前某个时间肯定会增加 - - 所以让我们调用触发此终点增加的范围(k,j-1)。显然总和(k,j-1)&lt; n,因为这是(根据定义)导致我们将终点从j-1增加到j的范围;并且就像k&lt; = i一样,因为我们只按其起点的递增顺序处理范围。由于i> = k,sum(i,j-1)与sum(k,j-1)恰好相同,但是从左端删除了零个或多个项,并且所有这些项都是正的,所以它必须是和(i,j-1)&lt; = sum(k,j-1)&lt; Ñ

所以我们已经确定每当我们将i增加到i + 1时,我们知道sum(i,j-1)&lt; ñ。为了完成对此规则的分析,我们(再次)需要使用的是从该总和的任何一端删除术语不能使其更大。删除第一项使得我们得到sum(i + 1,j-1)&lt; = sum(i,j-1)&lt; ñ。从开始求和并从另一端连续删除项使得我们得到sum(i + 1,j-2),sum(i + 1,j-3),...,sum( i + 1,i + 1),我们所知道的所有都必须小于n - 也就是说,与这些和相对应的范围都不是有效的解。因此,我们可以安全地避免首先考虑它们,这正是算法的作用。

最后一个潜在的绊脚石似乎是,由于我们正在推进两个循环索引,因此时间复杂度应为O(m ^ 2)。但请注意,每次通过循环体时,我们将其中一个索引(i或j)推进一个,我们永远不会向后移动,所以如果我们仍然在2m循环迭代后运行我们必须有i + j = 2m。由于两个索引都不能超过m,因此唯一的方法就是i = j = m,这意味着我们已经到达终点:即我们保证在最多2m次迭代后终止。

答案 4 :(得分:0)

我知道这个问题有点老了,但是我不能回避先前答案中的分析。实际上,已经强调了所有三个提议的算法的运行时间在n中基本上是线性的。但实际上,产生运行在n严格较小的幂上的算法并不难。

要查看操作方法,让我们在1到K之间选择一个参数n,并假设我们所需的素数已经制成表格(如果必须从头开始计算,请参见下文)。然后,这就是我们要做的工作,以n个连续素数之和来搜索k的表示形式:

  • 首先,我们使用杰里·科芬的答案中的想法搜索k<K;也就是说,我们搜索k周围的n/k个质数。
  • 然后使用方言答案中解释的算法来探索k>=K素数的和;也就是说,我们从第一个元素为2的总和开始,然后一次将第一个元素前进一步。

第一部分涉及大素数的短和,它要求O(log n)操作对二个接近n/k的素数进行二进制搜索,然后再进行O(k)操作来搜索另一个{{1 }}质数(有一些简单的可能的实现)。总而言之,这将使运行时间

k

第二部分,即小素数的长和,要求我们考虑连续素数R_1=O(K^2)+O(Klog n).的和,其中第一个元素最多为p_1<\dots<p_k。 因此,它最多需要访问n/K个质数(实际上可以通过质数定理的弱形式来保存对数因子)。由于算法中每个质数最多被访问n/K+K次,因此运行时间为

O(1)

现在,如果有R_2=O(n/K) + O(K).,则第一部分以log n < K < \sqrt n操作运行,第二部分以O(K^2)运行。我们通过选择O(n/K)进行优化,因此总运行时间为

K=n^{1/3}

如果没有列出质数

如果我们还必须找到素数,这就是我们的方法。 首先,我们使用Erathostenes,它在R_1+R_2=O(n^{2/3}).操作中找到直至C_2=O(T log log T)的所有素数,其中T是算法第二部分中访问的小素数的上限。

为了执行算法的第一部分,我们需要为每个T=O(n/K)找到位于k<K周围的O(k)个素数。黎曼假设意味着对于某个常数n/k,如果k的间隔[x,x+y]中至少有y>c log x (k+\sqrt x)个素数。因此,我们需要先验地找到以c>0为中心,宽度为I_k的间隔n/k中包含的素数。

使用筛检Eratosthenes筛检间隔|I_k|= O(k log n)+O(\sqrt {n/k} log n)需要进行I_k操作。如果O(|I_k|log log n) + O(\sqrt n),则每个k<K<\sqrt n的时间复杂度为C_1=O(\sqrt n log n log log n)

总结,当

时,时间复杂度k<K最大化。

C_1+C_2+R_1+R_2

通过这种选择具有次线性时间复杂度

K = n^{1/4} / (log n \sqrt{log log n}).

如果我们不假设黎曼假设,我们将不得不在较大的区间内进行搜索,但是最后我们仍然会得到亚线性时间复杂度。相反,如果我们假设对质子缺口有更强的猜想,那么我们可能只需要在宽度R_1+R_2+C_1+C_2 = O(n^{3/4}\sqrt{log log n}.的间隔I_k上搜索某个|I_k|=k (log n)^A。然后,我们可以使用其他确定性素数测试来代替Erathostenes。例如,假设您可以在A>0操作中测试单个数字的素数,对于某些O((log n)^B)。 然后您可以在B>0操作中搜索间隔I_k。在这种情况下,最佳O(k(log n)^{A+B})仍然是K,取决于对数因子,因此某些K\approx n^{1/3}的总复杂度为O(n^{2/3}(log n)^D