import Jam
import Data.List
import Data.Tree
import Text.ParserCombinators.Parsec
import Text.Printf
main = jam $ do
[n] <- getints
ss <- getsn n
[m] <- getints
qss <- map (tail . tail . words) <$> getsn m
let
Right root = parse dtree "" $ concat ss
g (Node (q, _) []) pr qs = pr * q
g (Node (q, s) [t, f]) pr qs = g (if elem s qs then t else f) (pr * q) qs
pure $ concat $ ("\n" ++) . printf "%.7f" . g root 1 <$> qss
dtree :: Parser (Tree (Double, String))
dtree = do
ws
char '('
ws
wt <- many1 (digit <|> char '.')
ws
(id, kids) <- option ("", []) subtree
char ')'
ws
return $ Node (read wt, id) kids
subtree = (,) <$> many1 letter <*> sequence [dtree, dtree]
ws = many (oneOf " \n")
Decision Tree
Parsec is a Haskell library well-suited for grammars like those in this problem:
The Next Number
A generalization of the problem of finding the next permutation lexicographic order.
We look for the rightmost pair of adjacent digits that appear in increasing order.
If no such pair exists, then the digits are in decreasing order, so we are forced to add a zero digit. Thus the next number can be constructed by starting with the minimum nonzero digit of the number, followed by all the zeros, including the zero we just added, then the remainder of the digits in increasing order.
We take tiny shortcuts: since the digits are in decreasing order the minimum nonzero digit is the rightmost nonzero digit, and we can simply reverse the other digits to sort.
Otherwise let (a, b) be the rightmost pair of adjacent digits such that a <
b. Then we find the smallest digit a' exceeding a to its right, and swap
them. Lastly we reverse the digits to the right of a', which puts them
in increasing order.
import Jam
import Data.List
main = jam $ do
cs <- gets
let spls = drop 2 $ reverse $ zip (inits cs) (tails cs) of
pure $ case find (\(_, a:b:_) -> a < b) spls of
Nothing -> b:'0':as ++ bs
where (as, b:bs) = span (== '0') $ reverse cs
Just (xs, y:ys) -> xs ++ b:as ++ y:bs
where (as, b:bs) = span (<= y) $ reverse ys
Square Math
Brute force works on the small input. We iteratively try all paths of a certain length to a given cell. If two paths to the same destination have the same sum, we discard the longer path, or the lexicographically greater path in case of a tie.
At each step, we see if any paths found so far sum to the first query. If so, we find the best path, then remove the first query. We repeat until there are no queries left.
import Jam
import Control.Arrow
import Data.Array
import Data.Char
import Data.List
import qualified Data.Map as M
import Data.Maybe
add (a, b) (c, d) = (a + c, b + d)
dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]
toNum '+' d = digitToInt d
toNum '-' d = -digitToInt d
main = jam $ do
[w, _] <- getints
a <- listArray ((1, 1), (w, w)) . concat <$> getsn w
qs <- getints
let
seed = M.fromList $ second f <$> filter (isDigit . snd) (assocs a)
where f d = M.singleton (digitToInt d) [d]
go (p, m) = [(p2, (n + dn, s ++ [c1, c2])) | p1 <- nbrs p, p2 <- nbrs p1,
(n, s) <- M.assocs m,
let [c1, c2] = map (a!) [p1,p2], let dn = toNum c1 c2]
nbrs p = filter (inRange $ bounds a) $ add p <$> dirs
best s t | length s < length t = s
| length s > length t = t
| otherwise = min s t
ins m (p, (n, s)) = M.insert p (M.insertWith best n s $ m M.! p) m
search [] acc _ = acc
search qs@(q:rest) acc m
| not $ null ans = search rest (acc ++ [foldl1' best ans]) m
| otherwise = search qs acc m'
where
ans = catMaybes $ M.lookup q <$> M.elems m
m' = foldl' ins m $ concatMap go $ M.assocs m
pure $ concatMap ("\n" ++) $ search qs [] seed
For the large input, a few optimizations were enough to bring the running time to about 6 or 7 minutes on my laptop:
-
We use strict maps.
-
We use the specialized
IntMapfor one of the maps. -
In the nth step, we only consider paths of length n.
-
In each step, we try all pending queries, and remove those that match.
-
We prepend e.g. "1+" to "2-3" instead of appending "-3" to "1+2", because we use
String.
However, this is almost too slow for the contest. Was I supposed to find some clever trick to speed things up?
So far I’ve only found one shortcut. First, divide each digit by the greatest
common divisor of all digits in the square. Next, let b be the biggest
digit that appears next to a + sign.
Suppose we find b consecutive positive integers whose best expressions all
contain +b. Then we can show that the best expressions for all larger
integers are the same except we replace one copy of +b with the appropriate
number of copies of +b.
I’m skeptical we’re supposed to exploit this, because the largest possible query is 250.
import Jam
import Control.Arrow
import Data.Array
import Data.Bool
import Data.Char
import Data.List
import qualified Data.Map.Strict as M
import qualified Data.IntMap.Strict as I
import Data.Maybe
add (a, b) (c, d) = (a + c, b + d)
dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]
main = jam $ do
[w, _] <- getints
a <- listArray ((1, 1), (w, w)) . concat <$> getsn w
qs <- getints
let
ds = [(p, nub [(p2, (digitToInt d - bool 0 (2 * digitToInt (a!p2))
(a!p1 == '-'), [d, a!p1])) | p1 <- nbrs p, p2 <- nbrs p1,
d /= '0' || p2 /= p]) | (p, d) <- assocs a, isDigit d]
seed = M.fromList [(p, I.singleton (digitToInt d) [d]) |
(p, d) <- assocs a, isDigit d]
nbrs p = filter (inRange $ bounds a) $ add p <$> dirs
next m = M.fromList $ map (second $ merge m) ds
merge m deltas = foldl1' (I.unionWith min) $ map (bump m) deltas
bump m (p, (dn, ds)) = (ds ++) <$> I.mapKeysMonotonic (dn +) (m M.! p)
search qs acc m
| null qs = acc
| otherwise = search todo (done ++ acc) m'
where
m' = next m
done = I.toList $ I.fromListWith min $ concatMap match $ M.elems m
todo = qs \\ (fst <$> done)
match m = catMaybes $ foo m <$> qs
foo m q = (,) q <$> (q `I.lookup` m)
pure $ concatMap ("\n" ++) $ (fromJust . (`lookup` search qs [] seed)) <$> qs