import Jam import Data.List import Data.Maybe s = "qzejp mysljylc kd kxveddknmc re jsicpdrysi rbcpc ypc rtcsra dkh wyfrepkym veddknkmkrkcd de kr kd eoya kw aej tysr re ujdr lkgc jv" t = "zqour language is impossible to understand there are twenty six factorial possibilities so it is okay if you want to just give up" main = jam $ map (fromMaybe '?' . (`lookup` nub (zip s t))) <$> gets missing = ['a'..'z'] \\ nub s
2012 Qualification Round
Speaking in Tongues
The lookup
function of Data.List
suffices for this simple problem.
Included are safety checks used during development. Evaluate missing
to
ensure we have handled all letters of the alphabet. In fact, this problem is
sneaky in that the mapping of the letters "q" and "z" can only be determined
from reading the description, and not the sample input and output.
Also, we print question marks for unhandled letters. There should be none in the output, which we can check before submitting our answer.
Dancing With the Googlers
When p
is 0 or 1, the problem is trivial.
Otherwise, define m = 3*(p - 1)
. We have three cases:
-
A total score exceeding
m
can be broken down into an unsurprising triple including at least one score that is at leastp
. -
A total score equalling
m
orm - 1
can be broken down into a triple including a score of at leastp
if and only if the triple is surprising. -
A total score below
m - 1
cannot be broken down into a triple containing a score that is at leastp
, surprising or not.
This leads to the following solution:
import Jam main = jam $ do (_:s:p:ts) <- getints let m = 3*(p - 1) pure $ show $ case p of 0 -> length ts 1 -> length (filter (> 0) ts) _ -> length (filter (> m) ts) + min s (length $ filter (`elem` [m, m - 1]) ts)
Recycled Numbers
Brute force works for the small input. We count all distinct pairs within
[a..b]
, such that one is the rotation of the other.
import Data.List import Jam rot (x:xs) = xs ++ [x] rots xs = take (length xs) $ iterate rot xs main = jam $ do [a, b] <- getints pure $ show $ length [undefined | n <- [a..b], m <- [n+1..b], show n `elem` rots (show m)]
For the large input, the quadratic running time from enumerating all pairs
is too steep. Instead, for each number n
in [a..b]
, we compute all its
unique rotations that lie within range, and count the number of ways to
choose two of these rotations, provided n
is the smallest of these rotations.
import Data.List import Jam rot (x:xs) = xs ++ [x] rots xs = take (length xs) $ iterate rot xs main = jam $ do [a, b] <- words <$> gets pure $ show $ sum $ f . nub . filter (b >=) . filter (a <=) . rots . show <$> [read a..(read b :: Int)] f ms@(m:_) | m == minimum ms = k * (k - 1) `div` 2 | otherwise = 0 where k = length ms
This last condition ensures we count each case exactly once. An alternative is to replace this last condition with a division by the number of rotations.
f ms = k * (k - 1) / (2 * k) where k = length ms
This leads to the simplification:
import Data.List import Jam rot (x:xs) = xs ++ [x] rots xs = take (length xs) $ iterate rot xs main = jam $ do [a, b] <- words <$> gets pure $ show $ (`div` 2) $ sum $ (+(-1)) . length . nub . filter (b >=) . filter (a <=) . rots . show <$> [read a..(read b :: Int)]
After reaching this point, I realized there is a simpler combinatorial argument for the above.
Suppose n
in [a..b]
has k
distinct valid rotations. This includes
itself, so k - 1
of these rotations are distinct to to n
. We tally them
all, that is we count each pair (n, n')
where n'
is a distinct rotation of
n
.
Ultimately, we have counted each pair twice, once for each of the two orderings, so we halve the count.
Hall of Mirrors
The conditions for the small input imply the room is empty, and the only mirrors are on the walls.
In the first example input, with Cartesian coordinates, our location is (0.5, 0.5) and the room is the unit square centered on this location.
To avoid fractions, we scale everything by a factor of 2, so that our location is now (1, 1), our room measures 2x2 (and is centered on our location) and light travels at most 2 units. We perform this transformation after reading the input.
Normally the x-axis is horizontal and the y-axis is vertical. We flip this convention for this problem; alternatively, we fiip the room along the diagonal 'y = x' before running our code, and the answer is the same.
Let (r, c)
be our coordinates, and let the room is defined by a diagonal
from (0, 0)
to (h, w)
. If (r, c)
is the exact center of the room, as it
is for the first two examples, then our image (or the original) appears at
points whose first coordinate differs from ours by an integer multiple of h
,
and whose second coordinate differs from ours by an integer multiple of w
.
That is, for some integers a, b
, their coordinates are translated by
(w * a, h * b)
from our location.
More generally, our location may be displaced from the center. Then
for any integers (a, b)
, we see images of ourselves translated from our
location by the four points:
[(2*w*a + i, 2*h*b + j) | i <- [0, -2*c], j <- [0, -2*r]]
We want the points in this list whose norm is at most d
, and for any
fixed angle, we want at most one point, because the images can block those
directly behind.
To avoid square roots, we work with squared norms.
import Data.Array import Data.List import Data.Set (Set) import qualified Data.Set as S import Jam main = jam $ do [h, w, d] <- getints a <- listArray ((1, 1), (h, w)) . concat <$> getsn h let Just (r, c) = fst <$> find ((== 'X') . snd) (assocs a) pure $ show $ f (2*r - 3, 2*c - 3) (2*h - 4, 2*w - 4) $ (2*d)^2 absSq (a, b) = a^2 + b^2 add (a, b) (c, d) = (a + c, b + d) neg (a, b) = (-a, -b) sub v w = add v $ neg w f (r, c) (h, w) d = S.size $ foldl' g S.empty $ concat $ [yu, yd] <*> xl ++ xr where v = takeWhile ((<= d) . absSq) xr = v $ map (flip (,) 0) $ (+) <$> [2*w, 4*w..] <*> [-2*c, 0] xl = v $ map (flip (,) 0) $ (+) <$> [0, -2*w..] <*> [0, -2*c] yu p = v $ map (add p . (,) 0) $ (+) <$> [2*h, 4*h..] <*> [-2*r, 0] yd p = v $ map (add p . (,) 0) $ (+) <$> [0, -2*h..] <*> [0, -2*r] g s (r, c) | d == 0 = s | otherwise = S.insert (r `div` d, c `div` d) s where d = gcd r c
Above we begin by exploring the x-axis starting from our location until the
norm exceeds d
, once for the positive direction and once for the negative.
Then for each point we found, we explore in the positive and negative y
directions until the norm exceeds d
.
We compute angles uniquely by dividing x- and y- coordinates by their
greatest common divisor; there is no need for atan2
.
For the large input, oddly shaped rooms prevent us from easily computing the
locations of our images. Instead, we must consider every integer point lying
within distance d
of the origin, and compute its angle to the origin. We then
trace a ray from us at each unique angle we find.
angs d = S.toList $ foldl' g S.empty $ concat $ [[(x, y), (x, -y), (-x, y), (-x, -y)] | x <- [0..d], y <- takeWhile ((<= d^2 - x^2) . (^2)) [0..]] g s (r, c) | d == 0 = s | otherwise = S.insert (r `div` d, c `div` d) s where d = gcd r c
Here’s one way to visualize why these are the only angles we need to check: Imagine the floor of the room is tiled with squares, and that mirrors must be placed on the edges of tiles. What do we see when we look at the reflections of the floor?
Unlike the small input, we keep the original scale. Each character of the input
describes the contents of a 1x1 square of the room. A "#" character at (x, y)
means the square defined by the diagonal (x, y)
to (x + 1, y + 1)
is a
square mirror. If "X" appears at (x, y)
in the array, then our coordinates
are (x + 1%2, y + 1%2)
; we’re using Data.Ratio
to handle fractions.
We simplify our code by reflecting the ray and the entire room horizontally or vertically so that both coordinates of our ray are positive.
We can transform arrays with ixmap
:
xflip a = ixmap b (\(x, y) -> (h1 + h0 - x, y)) a where b@((h0, _), (h1, _)) = bounds a yflip a = ixmap b (\(x, y) -> (x, w1 + w0 - y)) a where b@((_, w0), (_, w1)) = bounds a
However, my solution using this code wound up taking about three and a half minutes on my laptop. The running time halved when the above was replaced with a wrapper to handle the reflections:
data Flippy = Flippy Bool Bool (Array (Int, Int) Char) xflip (Flippy xf yf a) = Flippy (not xf) yf a yflip (Flippy xf yf a) = Flippy xf (not yf) a get (Flippy xflip yflip a) (x, y) = a!(bool x (h0 + h1 - x) xflip, bool y (w0 + w1 - y) yflip) where ((h0, w0), (h1, w1)) = bounds a
Even though we can assume the ray (dx, dy)
is in the first quadrant, the code
is a little tricky. We opt to trace the ray square by square: we determine if
the ray first hits the top edge, or the right edge, or both, look up the
corresponding characters in the input array, and then reflect or destroy the
ray if necessary.
Along the way, we maintain the distance traveled so far, in the form of a multiplier, and stop tracing if we exceed maximum visibility.
Checking to see if we’ve reached an image of ourselves is also tedious. We ensure we’ve traveled some distance to avoid counting ourselves as an image. Then if an "X" is present in the unit square where we are currently tracing the ray, we see if the ray will pass through the exact center of the square. If it does, we still need to check that the accumulated distance is at most the maximum visible distance.
import Data.Array import Data.Bool import Data.List import Data.Ratio import Data.Set (Set) import qualified Data.Set as S import Jam absSq (a, b) = a^2 + b^2 add (a, b) (c, d) = (a + c, b + d) fi (a, b) = (fromIntegral a, fromIntegral b) data Flippy = Flippy Bool Bool (Array (Int, Int) Char) get (Flippy xflip yflip a) (x, y) = a!(bool x (h0 + h1 - x) xflip, bool y (w0 + w1 - y) yflip) where ((h0, w0), (h1, w1)) = bounds a xflip (Flippy xf yf a) = Flippy (not xf) yf a yflip (Flippy xf yf a) = Flippy xf (not yf) a main = jam $ do [h, w, d] <- getints a <- listArray ((0, 0), (h - 1, w - 1)) . concat <$> getsn h let Just (x, y) = add (1%2, 1%2) . fi . fst <$> find ((== 'X') . snd) (assocs a) pure $ show $ sum $ ray (Flippy False False a) d 0 (x, y) . fi <$> angs d angs d = S.toList $ foldl' g S.empty $ concat $ [[(x, y), (x, -y), (-x, y), (-x, -y)] | x <- [0..d], y <- takeWhile ((<= d^2 - x^2) . (^2)) [0..]] g s (r, c) | d == 0 = s | otherwise = S.insert (r `div` d, c `div` d) s where d = gcd r c ray a@(Flippy _ _ arr) lim acc (x, y) (dx, dy) | not $ visible acc = 0 | dx < 0 = ray (xflip a) lim acc (h - x, y) (-dx, dy) | dy < 0 = ray (yflip a) lim acc (x, w - y) (dx, -dy) | acc > 0 && get a (qx, qy) == 'X' && (1%2 - rx) * dy == (1%2 - ry) * dx = fromEnum $ visible (acc + t / 2) | get a (floor px, floor py) /= '#' = ray a lim (acc + t) p (dx, dy) | get a (qx + 1, qy) /= '#' && get a (qx, qy + 1) /= '#' = 0 | otherwise = ray a lim (acc + t) p (bool dx (-dx) $ floor px > qx && get a (qx + 1, qy) == '#', bool dy (-dy) $ floor py > qy && get a (qx, qy + 1) == '#') where (h, w) = add (1, 1) $ fi $ snd $ bounds arr (qx, rx) = properFraction x (qy, ry) = properFraction y p@(px, py) = (x + t * dx, y + t * dy) tx = (1 - rx) / dx ty = (1 - ry) / dy t | dx == 0 = ty | dy == 0 = tx | otherwise = min tx ty visible v = v^2 * (dx^2 + dy^2) <= fromIntegral lim^2
The fi
helper function deals with type annoyances caused by the array being
indexed by integers, but our calculations being done on rationals.