Life Goes On

まあまあだけど楽しんでる方です

84問目

http://projecteuler.net/index.php?section=problems&id=84
モノポリーには 40 のマスがあるが、それぞれのマスで停まる確率を求め、TOP3を答える。
Project Euler では問題文が長いのは嫌われる傾向にあるみたいですが(54問目とか)、この問題は色々な要素があって、とても楽しかったです。
ある時点 n に、あるマスにいる確率を Pn としたとき、別のマスへ遷移する確率を求めて Pn と Pn+1 の漸化式を出します。定常状態では Pn = Pn+1 とおけて、これは40元連立1次方程式になるので、行列で表現して、ガウス・ジョルダン法で解いてます。
肝心の遷移確率ですが、サイコロの目で遷移する確率(ゾロ目を含む)を関数 p' で求めて、それにカードによる遷移を加味して関数 p を作っています。
プログラムが長くなった分、やはりバグが入りやすくなっていて、見つけるのに苦労しました。まだ数値が若干違うので(6面サイコロでJAILに停まる確率が 6.24% ではなく 6.30% になる)、もしかしたらバグが残っているかもしれません。

import Data.List
import Data.Ratio

main = print $ concat $ take 3 $ map snd $ euler084 eq

euler084 :: [[Rational]] -> [(Double, String)]
euler084 es = reverse $ sort $ zip (map (fromRational . last) $ foldl solve es [0..39]) digits
    where digits = [ [c1,c2] | c1 <- "0123456789", c2 <- "0123456789" ]

solve :: [[Rational]] -> Int -> [[Rational]]
solve es n = (\ (f, s) -> (map sub f) ++ (e' : (map sub $ tail s))) $ splitAt n es
    where e' = map (/ (es !! n !! n)) (es !! n)
          sub e = zipWith (-) e (map (* (e !! n)) e')

eq :: [[Rational]]
eq = [ [ (if m == n then 1 else 0) - (p m n) + 1 | m <- [0..39] ] ++ [1] | n <- [0..39] ]

p :: Integer -> Integer -> Rational
p m n
    | n ==  0 = (p' m n) + (cc m)/16 + (ch m)/16 -- GO
    | n == 10 = (p' m n) + (cc m)/16 + (ch m)/16 + (g2j m) -- JAIL
    | n == 11 = (p' m n) + (ch m)/16 -- C1
    | n == 24 = (p' m n) + (ch m)/16 -- E3
    | n == 39 = (p' m n) + (ch m)/16 -- H2
    | n ==  5 = (p' m n) + (ch m)/16 + (ch3 m)*2/16 -- R1
    | n == 15 = (p' m n) + (ch1 m)*2/16 -- R2
    | n == 25 = (p' m n) + (ch2 m)*2/16 -- R3
    | n == 12 = (p' m n) + (ch1 m)/16 + (ch3 m)/16 -- U1
    | n == 28 = (p' m n) + (ch2 m)/16 -- U2
    | n ==  4 = (p' m n) + (ch1 m)/16 -- 3 squares before CH1
    | n == 19 = (p' m n) + (ch2 m)/16 -- 3 squares before CH2
    | n ==  2 = (cc1 m)*14/16 -- CC1
    | n == 17 = (cc2 m)*14/16 -- CC2
    | n == 33 = (cc3 m)*14/16 -- CC3
    | n ==  7 = (ch1 m)*6/16 -- CH1
    | n == 22 = (ch2 m)*6/16 -- CH2
    | n == 36 = (ch3 m)*6/16 -- CH3
    | n == 30 = 0 -- G2J
    | otherwise = p' m n
    where cc  m = (cc1 m) + (cc2 m) + (cc3 m)
          cc1 m = p' m  2
          cc2 m = p' m 17
          cc3 m = p' m 33 + (ch3 m)/16 -- 3 squares before CC 3
          ch  m = (ch1 m) + (ch2 m) + (ch3 m)
          ch1 m = p' m  7
          ch2 m = p' m 22
          ch3 m = p' m 36
          g2j m = p' m 30

p' :: Integer -> Integer -> Rational
p' m n
    | d ==   0 = dub1
    | d <= 2*i = dub1 - dub0 + (min (d-1) (2*i-d+1))%(i^2)
    | otherwise = dub1
    where i = 4
          d = mod (n - m + 40) 40
          dub0 = if  even d then 1%(i^4) else 0
          dub1 = if n == 10 then 1%(i^3) else 0