Мы можем сделать это очень эффективно, создав структуру, которую мы можем индексировать за сублинейное время.
Но сначала
{-# LANGUAGE BangPatterns #-}
import Data.Function (fix)
Давайте определим f
, но сделаем так, чтобы он использовал «открытую рекурсию», а не вызывал сам себя.
f :: (Int -> Int) -> Int -> Int
f mf 0 = 0
f mf n = max n $ mf (n `div` 2) +
mf (n `div` 3) +
mf (n `div` 4)
Вы можете получить нематериальный f
, используя fix f
Это позволит вам проверить, что f
делает то, что вы имеете в виду для малых значений f
, вызывая, например: fix f 123 = 144
Мы могли бы запомнить это, определив:
f_list :: [Int]
f_list = map (f faster_f) [0..]
faster_f :: Int -> Int
faster_f n = f_list !! n
Это работает сносно хорошо и заменяет то, что должно было занять O (n ^ 3) время чем-то, что запоминает промежуточные результаты.
Но для индексации запомненного ответа для mf
все еще требуется линейное время, чтобы проиндексировать. Это означает, что результаты как:
*Main Data.List> faster_f 123801
248604
допустимы, но результат не намного лучше. Мы можем сделать лучше!
Сначала давайте определим бесконечное дерево:
data Tree a = Tree (Tree a) a (Tree a)
instance Functor Tree where
fmap f (Tree l m r) = Tree (fmap f l) (f m) (fmap f r)
И затем мы определим способ индексации в нем, чтобы мы могли найти узел с индексом n
в O (log n) времени вместо:
index :: Tree a -> Int -> a
index (Tree _ m _) 0 = m
index (Tree l _ r) n = case (n - 1) `divMod` 2 of
(q,0) -> index l q
(q,1) -> index r q
... и мы можем найти удобное дерево, полное натуральных чисел, поэтому нам не нужно возиться с этими индексами:
nats :: Tree Int
nats = go 0 1
where
go !n !s = Tree (go l s') n (go r s')
where
l = n + s
r = l + s
s' = s * 2
Поскольку мы можем индексировать, вы можете просто преобразовать дерево в список:
toList :: Tree a -> [a]
toList as = map (index as) [0..]
Пока что вы можете проверить работу, убедившись, что toList nats
дает вам [0..]
Сейчас
f_tree :: Tree Int
f_tree = fmap (f fastest_f) nats
fastest_f :: Int -> Int
fastest_f = index f_tree
работает так же, как и в приведенном выше списке, но вместо того, чтобы искать каждый узел за линейное время, он может отследить его в логарифмическом времени.
Результат значительно быстрее:
*Main> fastest_f 12380192300
67652175206
*Main> fastest_f 12793129379123
120695231674999
На самом деле это намного быстрее, чем вы можете пройти и заменить Int
на Integer
выше и получать смехотворно большие ответы почти мгновенно
*Main> fastest_f' 1230891823091823018203123
93721573993600178112200489
*Main> fastest_f' 12308918230918230182031231231293810923
11097012733777002208302545289166620866358