【问题标题】:How to solve Euler Project Prblem 303 faster?如何更快地解决 Euler Project 问题 303?
【发布时间】:2011-09-03 03:45:20
【问题描述】:

problem 是:

对于正整数 n,将 f(n) 定义为 n 的最小正倍数,以 10 为底,仅使用 ≤ 2 的数字。

因此 f(2)=2, f(3)=12, f(7)=21, f(42)=210, f(89)=1121222。


为了在 Mathematica 中解决这个问题,我编写了一个函数 f 来计算 f(n)/n:

f[n_] := Module[{i}, i = 1; 
While[Mod[FromDigits[IntegerDigits[i, 3]], n] != 0, i = i + 1]; 
Return[FromDigits[IntegerDigits[i, 3]]/n]]

原理很简单:使用三进制数字系统枚举所有带有0, 1, 2的数字,直到其中一个数字除以n

它正确地给出了11363107 1~100,我测试了1~1000(计算大约花了一分钟,并给出了111427232491),所以我开始计算问题的答案。

但是,这种方法太慢了。电脑已经计算了两个小时,还没算完。

如何改进我的代码以加快计算速度?

【问题讨论】:

  • 提示:尝试查看每个 f(k) 得到的值。只有最后几个需要很长时间才能计算出来,但你也许能找到一个模式:)
  • 所有项目 Euler 解决方案应在大约一分钟内运行。如果它在几个小时内运行,那么您使用的算法效率低下。
  • @Sjoerd C. de Vries 这就是我问它的原因..
  • 我认为这个问题还没有解决,所以我建议你删除“接受”标记。这可能会吸引更多观众关注您的问题。
  • 不应在开放论坛上讨论 Project Euler 问题。

标签: optimization wolfram-mathematica


【解决方案1】:

hammar 的评论清楚地表明,计算时间不成比例地花费在 n 的值上,这些值是 99 的倍数。我建议找到一种针对这些情况的算法(我将此作为练习留给读者)并使用 Mathematica 的模式匹配将计算定向到适当的。

f[n_Integer?Positive]/; Mod[n,99]==0  :=  (*  magic here *)
f[n_] := (* case for all other numbers *) Module[{i}, i = 1;
 While[Mod[FromDigits[IntegerDigits[i, 3]], n] != 0, i = i + 1];
 Return[FromDigits[IntegerDigits[i, 3]]/n]]

顺便说一句,您可以通过稍微不同的方式来加快快速简单的速度,但这当然是二阶改进。您也许可以将代码设置为最初使用ff,如果i 达到某个点,则打破While 循环,然后切换到您已经提供的f 函数。 (请注意,我在这里返回 n i 而不是 i - 这只是为了说明目的。)

ff[n_] := 
 Module[{i}, i = 1; While[Max[IntegerDigits[n i]] > 2, i++]; 
  Return[n i]]

Table[Timing[ff[n]], {n, 80, 90}]

{{0.000125, 1120}, {0.001151, 21222}, {0.001172, 22222}, {0.00059, 
  11122}, {0.000124, 2100}, {0.00007, 1020}, {0.000655, 
  12212}, {0.000125, 2001}, {0.000119, 2112}, {0.04202, 
  1121222}, {0.004291, 122220}}

对于短案例,这至少比您的版本(在下面复制)快一点,但对于长案例来说要慢得多。

Table[Timing[f[n]], {n, 80, 90}]

{{0.000318, 14}, {0.001225, 262}, {0.001363, 271}, {0.000706, 
 134}, {0.000358, 25}, {0.000185, 12}, {0.000934, 142}, {0.000316, 
 23}, {0.000447, 24}, {0.006628, 12598}, {0.002633, 1358}}

【讨论】:

  • 谢谢! ...是的...我发现了一些关于99, 999, ...99*5 之类的数字的模式,我认为我需要更多地考虑这些数字。
  • 顺便说一下,我发现Max[IntegerDigits[n i]] > 2Or @@ Thread[IntegerDigits[n i]>2] 快一点。
  • 你把最难的部分留给了读者……9999是一个特别难的部分。
【解决方案2】:

您可以做的一件简单的事情是将您的函数编译为C 并使其可并行化。

Clear[f, fCC]
f[n_Integer] := f[n] = fCC[n]
fCC = Compile[{{n, _Integer}}, Module[{i = 1},
   While[Mod[FromDigits[IntegerDigits[i, 3]], n] != 0, i++];
   Return[FromDigits[IntegerDigits[i, 3]]]],
  Parallelization -> True, CompilationTarget -> "C"];

Total[ParallelTable[f[i]/i, {i, 1, 100}]] 
(* Returns 11363107 *)

问题是最终您的整数将大于长整数,并且 Mathematica 将恢复为未编译的任意精度算术。 (我不知道为什么 Mathematica 编译器不包含任意精度的 C 库...)

正如 ShreevatsaR 评论的那样,如果您编写智能代码(并考虑数学),欧拉问题项目通常设计为快速运行,但如果您想强制执行它,则需要永远。请参阅about page。另外,spoilers posted on their message boards are removed 在其他网站上发布剧透被认为是不好的形式。


旁白:

您可以通过运行测试编译后的代码是否使用 32 位长度

In[1]:= test = Compile[{{n, _Integer}}, {n + 1, n - 1}];

In[2]:= test[2147483646]
Out[2]= {2147483647, 2147483645}

In[3]:= test[2147483647]
During evaluation of In[53]:= CompiledFunction::cfn: Numerical error encountered at instruction 1; proceeding with uncompiled evaluation. >>
Out[3]= {2147483648, 2147483646}

In[4]:= test[2147483648]
During evaluation of In[52]:= CompiledFunction::cfsa: Argument 2147483648 at position 1 should be a machine-size integer. >>
Out[4]= {2147483649, 2147483647}

对于负数也类似。

【讨论】:

    【解决方案3】:

    我确信一定有更好的方法来做到这一点,但这是我的灵感来源。

    以下代码查找 n 1-10,000 的所有 f[n] 值,除了最困难的值,恰好是 n = 9999。当我们到达那里时我停止循环。

    ClearAll[f];
    i3 = 1;
    divNotFound = Range[10000]; 
    While[Length[divNotFound] > 1, 
      i10 = FromDigits[IntegerDigits[i3++, 3]];
      divFound = Pick[divNotFound, Divisible[i10, divNotFound]];
      divNotFound = Complement[divNotFound, divFound];
      Scan[(f[#] = i10) &, divFound]
    ] // Timing
    

    Divisible 可能对两个参数的列表都有效,我们在这里充分利用了它。整个程序大约需要 8 分钟。

    对于 9999,需要进行一些思考。在合理的时间内它不是暴力破解的。

    令 P 为我们要寻找的因数,T(仅由 0、1 和 2 组成)为 P 与 9999 相乘的结果,即

    9999 P = T
    

    然后

    P(10,000 - 1) = 10,000 P - P = T
    
     ==> 10,000 P = P + T
    

    令 P1, ...PL 为 P 的数字,Ti 为 T 的数字,那么我们有

    和中的最后四个零当然来自乘以 10,000。因此 TL+1,...,TL+4 和 PL-3,...,PL 互为补码。前者仅由 0,1,2 组成,后者允许:

    last4 = IntegerDigits[#][[-4 ;; -1]] & /@ (10000 - FromDigits /@ Tuples[{0, 1, 2}, 4])
    
    ==> {{0, 0, 0, 0}, {9, 9, 9, 9}, {9, 9, 9, 8}, {9, 9, 9, 0}, {9, 9, 8, 9}, 
         {9, 9, 8, 8}, {9, 9, 8, 0}, {9, 9, 7, 9}, ..., {7, 7, 7, 9}, {7, 7, 7, 8}}
    

    只有 81 个允许的集合,包括 7、8、9 和 0(不是它们的所有可能组合)而不是 10,000 个数字,速度增益是 120 倍。

    可以看出P1-P4只能有三进制数,即三进制数和naught之和。您可以看到添加 T5 和 P1 不会产生结转。通过意识到 P1 不能为 0(第一个数字必须是某物),可以进一步减少,如果它是 2 与 9999 的乘积,将导致 T 的结果为 8 或 9(如果发生进位),即也不允许。那么它必须是1。 P2-P4 也可以排除二号。

    由于 P5 = P1 + T5,因此 P5

    在所有这些情况下,添加不需要包含结转,因为它们不会发生(Pi+Ti 始终

    结合所有这些,我们将尝试找到 L=16 的解决方案:

    Do[
      If[Max[IntegerDigits[
          9999 FromDigits[{1, 1, 1, 1, i5, i6, i7, i8, i9, i10, i11, i12}~
             Join~l4]]
         ] < 3, 
       Return[FromDigits[{1, 1, 1, 1, i5, i6, i7, i8, i9, i10, i11, i12}~Join~l4]]
      ], 
      {i5, 0, 3}, {i6, 0, 3}, {i7, 0, 3}, {i8, 0, 3}, {i9, 0, 5}, 
      {i10, 0, 5}, {i11, 0, 5}, {i12, 0, 6}, {l4,last4}
     ] // Timing
    
    ==> {295.372, 1111333355557778}
    

    实际上是 1,111,333,355,557,778 x 9,999 = 11,112,222,222,222,222,222

    我们可能已经猜到了

    f[9] = 12,222
    f[99] = 1,122,222,222
    f[999] = 111,222,222,222,222

    模式显然是 1 的数量每一步增加 1,连续 2 的数量增加 4。

    13 分钟,这超过了 Euler 项目的 1 分钟限制。也许我会很快研究一下。

    【讨论】:

      【解决方案4】:

      尝试更聪明的方法。

      构建一个函数 F(N),找出可以被 N 整除的 {0,1,2} 位的最小数。

      所以对于给定的 N,我们正在寻找的数字可以写成 SUM = 10^n * dn + 10^(n-1) * dn-1 .... 10^1 * d1 + 1* d0(其中 di 是数字的位数)。

      所以你必须找出 SUM % N == 0 的数字 基本上每个数字都有助于 SUM % N 与 (10^i * di) % N

      我不再给出任何提示,但下一个提示将是使用 DP。试着弄清楚如何使用 DP 来找出数字。

      对于 1 到 10000 之间的所有数字,在 C++ 中花费的时间不到 1 秒。 (总计)

      祝你好运。

      【讨论】:

        猜你喜欢
        • 2022-11-29
        • 2023-02-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-26
        • 2020-03-17
        • 2011-03-23
        相关资源
        最近更新 更多