以下是SPOJ的一个简单的编程问题:http://www.spoj.com/problems/PROBTRES/。
基本上,要求您为i和j之间的数字输出最大的Collatz循环。 ($ n $的Collatz循环是最终从$ n $到1的步数。)
我一直在寻找一种Haskell方法来解决比Java或C ++更具竞争性的问题(以便符合允许的运行时限制)。虽然一个简单的Java解决方案可以记住任何已经计算过的循环的循环长度。我没有成功地应用这个想法来获得Haskell解决方案。
我已经尝试了Data.Function.Memoize,以及使用此帖子中的想法的自制日志时间记忆技术:Memoization in Haskell?。不幸的是,memoization实际上使循环(n)的计算更慢。我相信减速来自于haskell方式的开销。 (我尝试使用已编译的二进制代码运行,而不是解释。)
我还怀疑简单地从i到j迭代数字可能代价很高($ i,j \ le10 ^ 6 $)。所以我甚至尝试使用来自http://blog.openendings.net/2013/10/range-trees-and-profiling-in-haskell.html的想法预先计算范围查询的所有内容。但是,这仍然会超过时间限制"错误。
你能帮忙告诉一个整洁的竞争Haskell计划吗?
谢谢!
答案 0 :(得分:4)
>>> 使用下面的方法,我可以submit an accepted answer to SPOJ。您可以查看here中的完整代码。
问题已界定0 < n < 1,000,000
。预先计算所有并将它们存储在数组中;然后冻结数组。该数组可用作自己的缓存/ memoization空间。
然后问题将减少到数组上的范围查询问题,这可以使用树非常有效地完成。
使用下面的代码,我可以在几分之一秒内得到1..1,000,000
的Collatz:
$ time echo 1000000 | ./collatz
525
real 0m0.177s
user 0m0.173s
sys 0m0.003s
注意下面的collatz
函数,在内部使用可变STUArray
,但它本身是一个纯函数:
import Control.Monad.ST (ST)
import Control.Monad (mapM_)
import Control.Applicative ((<$>))
import Data.Array.Unboxed (UArray, elems)
import Data.Array.ST (STUArray, readArray, writeArray, runSTUArray, newArray)
collatz :: Int -> UArray Int Int
collatz size = out
where
next i = if odd i then 3 * i + 1 else i `div` 2
loop :: STUArray s Int Int -> Int -> ST s Int
loop arr k
| size < k = succ <$> loop arr (next k)
| otherwise = do
out <- readArray arr k
if out /= 0 then return out
else do
out <- succ <$> loop arr (next k)
writeArray arr k out
return out
out = runSTUArray $ do
arr <- newArray (1, size) 0
writeArray arr 1 1
mapM_ (loop arr) [2..size]
return arr
main = do
size <- read <$> getLine
print . maximum . elems $ collatz size
为了对此数组执行范围查询,您可以构建一个平衡树,如下所示:
type Range = (Int, Int)
data Tree = Leaf Int | Node Tree Tree Range Int
build_tree :: Int -> Tree
build_tree size = loop 1 cnt
where
ctz = collatz size
cnt = head . dropWhile (< size) $ iterate (*2) 1
(Leaf a) +: (Leaf b) = max a b
(Node _ _ _ a) +: (Node _ _ _ b) = max a b
loop lo hi
| lo == hi = Leaf $ if size < lo then minBound else ctz ! lo
| otherwise = Node left right (lo, hi) (left +: right)
where
i = (lo + hi) `div` 2
left = loop lo i
right = loop (i + 1) hi
query_tree :: Tree -> Int -> Int -> Int
query_tree (Leaf x) _ _ = x
query_tree (Node l r (lo, hi) x) i j
| i <= lo && hi <= j = x
| mid < i = query_tree r i j
| j < 1 + mid = query_tree l i j
| otherwise = max (query_tree l i j) (query_tree r i j)
where mid = (lo + hi) `div` 2
答案 1 :(得分:2)
这与其他答案中的相同,但是使用了一个不可变的递归定义数组(并且它也有轻微泄漏(有人会说为什么?)并且因此慢了两倍):
import Data.Array
upper = 10^6
step :: Integer -> Int
step i = 1 + colAt (if odd i then 3 * i + 1 else i `div` 2)
colAt :: Integer -> Int
colAt i | i > upper = step i
colAt i = col!i
col :: Array Integer Int
col = array (1, upper) $ (1, 1) : [(i, step i) | i <- [2..upper]]
main = print $ maximum $ elems col