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
。
它正确地给出了{1}} 1~100,我测试了1~1000(计算花了大约一分钟,然后给出11363107
),所以我开始计算问题的答案。
但是,这种方法太慢了。计算机已经计算了两个小时的答案,但还没有完成计算。
如何改进我的代码以更快地计算?
答案 0 :(得分:5)
您可以做的一件简单事情就是将您的函数编译为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}
和负数相似。
答案 1 :(得分:5)
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
,如果While
达到某个点,则会中断i
循环,然后切换到您已提供的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}}
答案 2 :(得分:3)
我相信必须有更好的方法来做到这一点,但这就是我的灵感来自我。
下面的代码找到f [n]的所有值为n 1-10,000,除了最困难的一个,恰好是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只能有三进制数字,即三元数和无数字的总和。您可以看到添加T5和P1不会遗留任何内容。通过认识到P1不能为0(第一个数字必须是某个东西)可以获得进一步的减少,如果它是2999乘以9999将导致T的结果中的8或9(如果发生进位)也不允许。它必须是1然后。对于P2-P4,也可以排除两个。
由于P5 = P1 + T5,因此P5
在所有这些情况下,添加不需要包括残留,因为它们不会发生(Pi + Ti总是<8)。如果L = 16,则P12可能不是这样。在这种情况下,我们可以通过添加最后4位数来进行结转。所以P12 <7。这也将P12排除在4位数的最后一个块中。因此,解决方案必须至少为16位数。
结合所有这些,我们将尝试找到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分钟,这超过了项目欧拉的1分钟限制。也许我会很快调查一下。
答案 3 :(得分:3)
尝试更聪明的东西。
建立一个函数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的数字 基本上每个数字对于具有(10 ^ i * di)%N
的SUM%N有贡献我不再提供任何提示,但下一个提示是使用DP。尝试弄清楚如何使用DP来找出数字。
对于1到10000之间的所有数字,在C ++中花费不到1秒。 (总计)
祝你好运。