Я думаю, что ты выстрелил себе в ногу, пытаясь быть слишком умным. Ниже я покажу прямую реализацию немного другого алгоритма, который примерно в 5 раз быстрее, чем ваш код на Haskell.
Вот основные комбинаторные вычисления. Учитывая количество символов в подстроке, мы можем вычислить число палиндромов максимальной длины следующим образом:
- Разделите все частоты на две, округляя вниз; Назовите это div2-частотами. Нам также понадобятся частоты mod2, которые представляют собой набор букв, для которых нам пришлось округлить.
- Суммируйте div2-частоты, чтобы получить общую длину префикса палиндрома; его факториал дает пересчет числа возможных префиксов для палиндрома.
- Возьмите произведение факториалов от div2-частот. Это говорит о том факторе, по которому мы переоценены выше.
- Возьмите размер мод2-частот или выберите 1, если их нет. Мы можем расширить любой из префиксов палиндрома на одно из значений в этом наборе, если таковые имеются, поэтому мы должны умножить на этот размер.
Для шага перерасчета для меня не очень очевидно, будет ли быстрее хранить предварительно вычисленные инверсии для факториалов и брать их продукт, или же быстрее будет просто взять продукт всех факториалов и выполнить одну обратную операцию в самый конец. Я сделаю последнее, потому что интуитивно кажется, что быстрее сделать одну инверсию на запрос, чем один поиск на повторяющиеся буквы, но что я знаю? Должно быть легко проверить, если вы хотите попробовать адаптировать код самостоятельно.
Есть только одно быстрое понимание, которое я имел против вашего кода, это то, что мы можем кэшировать счетчики частоты для префиксов ввода; затем вычисление подсчета частоты для подстроки представляет собой просто поточечное вычитание двух отсчетов в кэше. Я считаю, что ваши предварительные вычисления на входе немного завышены.
Без лишних слов, давайте посмотрим код. Как обычно, есть преамбула.
module Main where
import Control.Monad
import Data.Array (Array)
import qualified Data.Array as A
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Monoid
Как и вы, я хочу сделать все мои вычисления на дешевых Int
s и выпекать в модульных операциях, где это возможно. Я сделаю newtype
, чтобы убедиться, что это произойдет для меня.
newtype Mod1000000007 = Mod Int deriving (Eq, Ord)
instance Num Mod1000000007 where
fromInteger = Mod . (`mod` 1000000007) . fromInteger
Mod l + Mod r = Mod ((l+r) `rem` 1000000007)
Mod l * Mod r = Mod ((l*r) `rem` 1000000007)
negate (Mod v) = Mod ((1000000007 - v) `rem` 1000000007)
abs = id
signum = id
instance Integral Mod1000000007 where
toInteger (Mod n) = toInteger n
quotRem a b = (a * b^1000000005, 0)
Я пекла в основании 1000000007
в нескольких местах, но это легко обобщить, дав Mod
фантомный параметр и сделав класс HasBase
, чтобы выбрать базу. Задайте новый вопрос, если вы не уверены, как и заинтересованы; Я буду рад сделать более тщательную рецензию. Есть еще несколько экземпляров для Mod
, которые в основном неинтересны и в первую очередь нужны из-за иерархии числовых классов в Haskell:
instance Show Mod1000000007 where show (Mod n) = show n
instance Real Mod1000000007 where toRational (Mod n) = toRational n
instance Enum Mod1000000007 where
toEnum = Mod . (`mod` 1000000007)
fromEnum (Mod n) = n
Вот предварительное вычисление, которое мы хотим сделать для факториалов ...
type FactMap = Array Int Mod1000000007
factMap :: Int -> FactMap
factMap n = A.listArray (0,n) (scanl (*) 1 [1..])
... и для предварительного расчета частотных карт для каждого префикса, плюс получение частотной карты с учетом начальной и конечной точек.
type FreqMap = Map Char Int
freqMaps :: String -> Array Int FreqMap
freqMaps s = go where
go = A.listArray (0, length s)
(M.empty : [M.insertWith (+) c 1 (go A.! i) | (i, c) <- zip [0..] s])
substringFreqMap :: Array Int FreqMap -> Int -> Int -> FreqMap
substringFreqMap maps l r = M.unionWith (-) (maps A.! r) (maps A.! (l-1))
Реализация описанного выше базового вычисления - это всего лишь несколько строк кода, теперь у нас есть подходящие Num
и Integral
экземпляры для Mod1000000007
:
palindromeCount :: FactMap -> FreqMap -> Mod1000000007
palindromeCount facts freqs
= toEnum (max 1 mod2Freqs)
* (facts A.! sum div2Freqs)
`div` product (map (facts A.!) div2Freqs)
where
(div2Freqs, Sum mod2Freqs) = foldMap (\n -> ([n `quot` 2], Sum (n `rem` 2))) freqs
Теперь нам просто нужен короткий драйвер для чтения и передачи его соответствующим функциям.
main :: IO ()
main = do
inp <- getLine
q <- readLn
let freqs = freqMaps inp
facts = factMap (length inp)
replicateM_ q $ do
[l,r] <- map read . words <$> getLine
print . palindromeCount facts $ substringFreqMap freqs l r
Вот и все. Примечательно, что я не пытался увлекаться побитовыми операциями и не делал ничего особенного с аккумуляторами; все в том, что я считаю идиоматическим чисто функциональным стилем. Окончательный счет - примерно вдвое меньше кода, который выполняется примерно в 5 раз быстрее.
P.S. Ради интереса я заменил последнюю строку на print (l+r :: Int)
... и обнаружил, что примерно половина времени тратится на read
. Ой! Кажется, есть еще много висячих фруктов, если это еще недостаточно быстро.