10

我再次从 Project Euler 挑战中获得乐趣,我注意到我的 12 号解决方案是我~593.275 ms每次运行速度最慢的解决方案之一。这是我在~1254.593 ms每次运行时获得第 10 号的解决方案的第二位。我所有其他的答案都比3 ms在大多数情况下运行的时间都少1 ms

我对问题 12的 Java 解决方案:

主要的():

int index = 1;
long currTriangleNum = 1;

while (numDivisors(currTriangleNum) <= 500) {
    index++;
    currTriangleNum += index;
}

System.out.println(currTriangleNum);

数除数():

public static int numDivisors(long num) {  
    int numTotal = 0;

    if (num > 1)
        if (num % 2 == 0) {
            for (long i = 1; i * i <= num; i++)
                if (num % i == 0)
                    numTotal+=2;
        } else {
            // halves the time for odd numbers
            for (long i = 1; i * i <= num; i+=2)
                if (num % i == 0)
                    numTotal+=2;
    }
    else if (num == 0)
        return 0;
    else if (num == 1)
        return 1;
    else (num < 0)
        return numDivisors(num *= -1);

    return numTotal;
 }

.

环顾解决方案论坛,有人发现这些公式(n = (p^a)(q^b)(r^c)... & d(n) = (a+1)(b+1)( c+1)...) 为他们工作,但我个人不认为它会更快;也许手动更快,但不是在程序中。

.

基本的思考过程如下:

我们要计算 48 中的除数个数。通过查看下面的因子树,我们可以得出48 = (2^4)(3^1)[n = (p^a)(q^b)(r^c)...] 的结论。

  48
 /  \
2   24
   /  \
  2   12
     /  \
    2   06
       /  \
      2    3 

知道了这一点,我们构造公式d(48) = (4+1)(1+1)[d(n) = (a+1)(b+1)(c+1)...] 来确定 48 有 10 个因子。

d(n)  = (a+1)(b+1)(c+1)...
d(48) = (4+1)(1+1)
d(48) = (5)(2)
d(48) = 10

.

如何优化我的代码?这些公式是最好的解决方案吗?我觉得找到所有主要因素,然后实施公式将比我已经拥有的程序花费更长的时间。

非常感谢,

贾斯蒂安

编辑:

在任何人开始发布链接之前,我已经在 SO 中查看了类似的问题,但没有任何运气 - 我只是想不出他们的方法的实现会比我已有的方法运行得更快。

编辑2:

我在 Eratosthenes 筛上的第二次尝试(针对第 10 题):

int p = 3, n = 2000000;
long total = 0;
boolean[] sieve = new boolean[n];

for (int i = 3; i < n; i += 2)
    sieve[i] = true;

sieve[2] = true;

while (p * p < n) {
    for (int i = p; i < n; i++)
        if (sieve[i] && (i % p) == 0)
            sieve[i] = false;
    p++;

    while (!sieve[p])
        p++;
}

for (int i = 0; i < n; i++)
    if (sieve[i])
        total += i;

System.out.println(total);

运行速度~985.399 ms- 不比其他方法快太多,但尚未优化。但是,它有效。

4

3 回答 3

10

使用底层数学结构,这将极大地改变你程序的运行时间。顺便说一句,这也适用于问题 10;如果你不能在几毫秒内完成,你就使用了一个非常低效的算法。事实上,我建议你先解决问题 10,因为问题 12 是建立在它之上的。

我将为下面的问题 12 提供一个更好的算法,但首先,这是一个可以显着加快程序速度的观察结果。如果两个数 x 和 y 互质(即它们没有除 1 以外的公约数),则 d(x·y) = d(x)·d(y)。特别地,对于三角形数,d(n·(n+1)) = d(n)·d(n+1)。因此,不是迭代三角形数 n·(n+1),而是迭代 n:这将显着减小传递给 d(n) 的参数的大小。

如果您进行该优化,您会注意到您连续计算 d(n) 两次(一次为 d((n-1)+1),一次为 d(n))。这表明缓存 d 的结果是一个好主意。下面的算法做到了,但也计算 d 自下而上而不是自上而下,这更有效,因为乘法比因式分解要快得多。


问题 10可以通过Eratosthenes 筛的简单应用来解决。填充大小为 2000000 的布尔数组(即位向量),使得sieve[i]==trueifi是素数;然后总结其中的数字sieve[i]==true

问题 12可以通过 Eratosthenes 筛的推广来解决。与其制作sieve[i]一个表示是否i为素数的布尔值,不如将其设为一个数字,表示它为非素数的方式的数量,即 的除数的数量i。很容易修改 Eratosthenes 的基本筛子来做到这一点:而不是设置sieve[x*y]false,向它添加 1。

随后的几个项目欧拉问题都受益于类似的方法。

您可能遇到的一个问题是,在问题 12 中,不清楚何时停止计算筛子。您可以采用两种方法:
1. 按需按块计算筛子,这本身就是一个有价值的编程练习(这将需要比第二种方法更复杂的代码)
2. 或从高估界限开始:找到一些三角形数有超过 500 个除数,你知道你会在那个数字之前或那个数字上停下来。

如果您意识到您只需要关心奇数,您可以获得更多时间,因为 d(2^k·n) = (k+1)·d(n) 如果 n 是奇数,并且只给出 k 和 n (2^k·n) 在二进制计算机上速度很快。我将把优化的细节留作练习。

于 2010-08-01T16:26:08.320 回答
0

您是否考虑过分解素数并跟踪素数以便不必重新计算它们?

于 2010-08-01T15:52:39.063 回答
0

我前一阵子做过这个,所以我不记得所有的优化,这里有一些:

  1. 对 sum(1...n) 使用求和公式
  2. 使用问题 7 和问题 10 中描述的方法找到主要因素
  3. 根据其素数分解确定 n 有多少个除数*

*如果您不知道“规则”,请将此视为概率问题。例如,你有四种口味可以添加到咖啡中,你有多少选择?

于 2010-08-02T01:27:05.707 回答