我正在尝试解决Euler问题78,它基本上要求第一个数字partition function p(n)可以被1000000整除。
我使用基于五边形数字的Euler递归公式(在pents
中与正确的符号一起计算)。这是我的代码:
ps = 1 : map p [1..] where
p n = sum $ map getP $ takeWhile ((<= n).fst) pents where
getP (pent,sign) = sign * (ps !! (n-pent))
pents = zip (map (\n -> (3*n-1)*n `div` 2) $ [1..] >>= (\x -> [x,-x]))
(cycle [1,1,-1,-1])
虽然ps
似乎产生了正确的结果,但它太慢了。有没有办法加速计算,还是我需要一种完全不同的方法?
答案 0 :(得分:4)
xs !! n
具有线性复杂性。您应该尝试使用对数或常量访问数据结构。
编辑:这是我通过复制a similar one by augustss:
提出的快速实现psOpt x = psArr x
where psCall 0 = 1
psCall n = sum $ map getP $ takeWhile ((<= n).fst) pents where
getP (pent,sign) = sign * (psArr (n-pent))
psArr n = if n > ncache then psCall n else psCache ! n
psCache = listArray (0,ncache) $ map psCall [0..ncache]
在ghci中,我发现你的列表版本没有惊人的加速。没有运气!
修改:
事实上,正如Chris Kuklewicz所建议的-O2
,这个解决方案比n=5000
的解决方案快8倍。结合Hammar对模数为10 ^ 6的总和的洞察力,我得到了一个足够快的解决方案(在我的机器上大约10秒内找到希望正确的答案):
import Data.List (find)
import Data.Array
ps = 1 : map p [1..] where
p n = sum $ map getP $ takeWhile ((<= n).fst) pents where
getP (pent,sign) = sign * (ps !! (n-pent))
summod li = foldl (\a b -> (a + b) `mod` 10^6) 0 li
ps' = 1 : map p [1..] where
p n = summod $ map getP $ takeWhile ((<= n).fst) pents where
getP (pent,sign) = sign * (ps !! (n-pent))
ncache = 1000000
psCall 0 = 1
psCall n = summod $ map getP $ takeWhile ((<= n).fst) pents
where getP (pent,sign) = sign * (psArr (n-pent))
psArr n = if n > ncache then psCall n else psCache ! n
psCache = listArray (0,ncache) $ map psCall [0..ncache]
pents = zip (map (\n -> ((3*n-1)*n `div` 2) `mod` 10^6) $ [1..] >>= (\x -> [x,-x]))
(cycle [1,1,-1,-1])
(我打破了psCache抽象,所以你应该使用psArr
而不是psOpt
;这可以确保对psArr
的不同调用将重用相同的memoized数组。这对你很有用写find ((== 0) . ...)
......好吧,我认为最好不要发布完整的解决方案。)
感谢大家的额外建议。
答案 1 :(得分:1)
我没有做过欧拉问题,但通常在欧拉问题上有一个聪明的伎俩可以加速计算。
当我看到这个时:
sum $ map getP $ takeWhile ((<=n).fst) pents
我不禁想到,每次计算sum . map getP
元素时都必须有一个更聪明的方法来调用ps
。
现在我看一下......首先执行总和,然后乘法,而不是为每个元素和执行乘法(在getP
内)不是更快总结?
通常情况下,我会深入了解并提供正常工作的代码;但这是一个欧拉问题(不想破坏它!),所以我会在这里停留一些想法。
答案 2 :(得分:1)
受你的问题的启发,我用你的方法用Haskell解决了Euler 78。所以我可以给你一些表现提示。
您的缓存列表应该很好。
选择一些大数字maxN来绑定搜索(p n)。
计划是使用(Array Int64 Integer)来记忆(p n)的结果,其下限为0,上限为maxN。这需要根据数组以'p'和'p'的形式定义数组,它们是相互递归定义的:
重新定义(p n)到(pArray n)以查找数组A中对'p'的递归调用。
使用新的pArray和Data.Array.IArray.listArray创建数组A.
绝对用'ghc -O2'编译。这在13秒内运行。