1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HaskellでEducational DP Contest(A~I)を解く

Last updated at Posted at 2026-01-03

はじめに

この冬休みを使ってDPコンテストに挑戦しました。
以前はEまでやったことがありましたが今回は頑張ってIまで解きました。
DPの問題をHaskellで解く場合思いつくままにImmutableになんとかしようとするとTLEとかMLEとかになりやすいので結局STモナドを使うのが間違いがないという考えで落ち着いています。

A - Frog1

さてこの問題は配るDPで実装しました。STモナドを使っているので処理するかどうかの場合分けをwhen(やif)で切り分けられます。以下の例はwhenで場合分けしていますがDPテーブルを余裕をもって広く取っておくとwhenでの場合分けをなくすこともできます。

main = do
  n <- readLn :: IO Int
  hs <- readInts
  let arrH = A.listArray (1,n) hs :: A.UArray Int Int
  let ans = solve n arrH
  print ans

solve n arrH = runST $ do
  arrDP <- AM.newArray (1,n) (maxBound::Int) :: ST s (STUArray s Int Int) -- 型に注意
  AM.writeArray arrDP 1 0 -- DPテーブルの初期値設定
  -- 配るDP
  forM_ [1..n-1] $ \i -> do
    cost0 <- AM.readArray arrDP i
    when (i+1 <= n) $ do
      cost1 <- AM.readArray arrDP (i+1)
      let cost = abs $ arrH A.! (i+1) - arrH A.! i
      AM.writeArray arrDP (i+1) $ min cost1 (cost0+cost)
    when (i+2 <= n) $ do
      cost2 <- AM.readArray arrDP (i+2)
      let cost = abs $ arrH A.! (i+2) - arrH A.! i
      AM.writeArray arrDP (i+2) $ min cost2 (cost0+cost)
  AM.readArray arrDP n

提出

B - Frog2

A問題の遷移先2からKに増える部分に対応する。

main = do
  [n,k] <- readInts
  hs <- readInts
  let arrH = A.listArray (1,n) hs :: A.UArray Int Int
  let ans = solve n k arrH
  print ans

solve n k arrH = runST $ do
  arrDP <- AM.newArray (1,n) (maxBound::Int) :: ST s (STUArray s Int Int) -- 型に注意
  AM.writeArray arrDP 1 0 -- DPテーブルの初期値設定
  -- 配るDP
  forM_ [1..n-1] $ \i -> do
    cost0 <- AM.readArray arrDP i
    forM_ [1..k] $ \k -> do
      when (i+k <= n) $ do
        cost1 <- AM.readArray arrDP (i+k)
        let cost = abs $ arrH A.! (i+k) - arrH A.! i
        AM.writeArray arrDP (i+k) $ min cost1 (cost0+cost)
  AM.readArray arrDP n

提出

C - Vacation

遷移先が(A/B/C)の3️⃣つしかないのでImmutableに処理できHaskellで書きやすくてこのタイプのDPは楽しい。

main = do
  n <- readLn :: IO Int
  ((a0,b0,c0):abcs) <- replicateM n $ do
    [a,b,c] <- readInts
    return (a,b,c)
  print $ solve (a0,b0,c0) abcs

solve :: (Int,Int,Int) -> [(Int,Int,Int)] -> Int
solve (a,b,c) [] = maximum [a,b,c]
solve (a0,b0,c0) ((a,b,c):abcs) = solve (a1,b1,c1) abcs
  where
    a1 = max (b0+a) (c0+a)
    b1 = max (a0+b) (c0+b)
    c1 = max (a0+c) (b0+c)

提出

D - Knapsack 1

DPといえばナップサック問題ですね。以下は集めるDPで解いています。
DP配列を2次元にする必要がある。

main = do
  [n,wMax] <- readInts
  wvs <- replicateM n $ do
    [w,v] <- readInts
    return (w,v)
  let arrWV = A.listArray (1,n) wvs :: A.Array Int (Int,Int)
  let ans = solve n wMax arrWV
  print ans

-- 集めるDP
solve n wMax arrWV = runST $ do
  arrDP <- AM.newArray ((0,0),(n,wMax)) (minBound::Int) :: ST s (STUArray s (Int,Int) Int) -- 型に注意
  AM.writeArray arrDP (0,0) 0 -- DPテーブルの初期値設定
  forM_ [1..n] $ \i -> do
    let (w,v) = arrWV A.! i
    forM_ [0..wMax] $ \j -> do
      dp0 <- AM.readArray arrDP (i-1,j)
      if j-w >= 0 then do
        dp1 <- AM.readArray arrDP (i-1,j-w)
        let value1 = max (dp1 + v) dp0
        AM.writeArray arrDP (i,j) value1
      else
        AM.writeArray arrDP (i,j) dp0
  ans <- forM [0..wMax] $ \i -> do AM.readArray arrDP (n,i)
  return $ maximum ans

提出

E - Knapsack 2

ナップサック問題の亜種です。成約からDP配列の横幅をWeightにできないので
取りうるValueの値を横幅にします。

vMax = 100000

main = do
  [n,wMax] <- readInts
  wvs <- replicateM n $ do
    [w,v] <- readInts
    return (w,v)
  let arrWV = A.listArray (1,n) wvs :: A.Array Int (Int,Int)
  let ans = solve n wMax arrWV
  print ans

-- 集めるDP
solve n wMax arrWV = runST $ do
  arrDP <- AM.newArray ((0,0),(n,vMax)) (maxBound::Int) :: ST s (STUArray s (Int,Int) Int) -- 型に注意
  AM.writeArray arrDP (0,0) 0 -- DPテーブルの初期値設定
  forM_ [1..n] $ \i -> do
    let (w,v) = arrWV A.! i
    forM_ [0..vMax] $ \j -> do
      dp0 <- AM.readArray arrDP (i-1,j)
      if j-v >= 0 then do
        dp1 <- AM.readArray arrDP (i-1,j-v)
        if ( dp1 /= (maxBound::Int) && (dp1+w) <= wMax ) then do
          let value1 = min (dp1 + w) dp0
          AM.writeArray arrDP (i,j) value1
        else
          AM.writeArray arrDP (i,j) dp0
      else
        AM.writeArray arrDP (i,j) dp0
  ans <- forM [0..vMax] $ \i -> do
                                    val <- AM.readArray arrDP (n,i)
                                    return (i,val)
  ans2 <- filterM (\(a,b) -> return (b /= (maxBound::Int))) ans
  return $ maximum $ map fst ans2

提出

F - LSC

DPテーブルの処理は手続き型っぽくやる想定であればなんとかなる。

main = do
  s <- getLine
  t <- getLine
  let lenS = length s
  let arrS = AU.listArray (1,lenS) s :: AU.UArray Int Char
  let lenT = length t
  let arrT = AU.listArray (1,lenT) t :: AU.UArray Int Char
  let arrDP = dpSolve arrS arrT
  putStrLn $ reverse $ backTrack arrDP arrS arrT 

backTrack arrDP arrS arrT = step lenS lenT
  where
    (_,lenS) = AU.bounds arrS
    (_,lenT) = AU.bounds arrT
    step 0 _ = []
    step _ 0 = []
    step i j
      | arrS AU.! i == arrT AU.! j = arrT AU.! j : step (i-1) (j-1)
      | arrDP AU.! (i-1,j) >= arrDP AU.! (i,j-1) = step (i-1) j
      | otherwise = step i (j-1)
    

dpSolve arrS arrT = runSTUArray $ do
  let (_,lenS) = AU.bounds arrS
  let (_,lenT) = AU.bounds arrT
  arrDP <- AM.newArray ((0,0),(lenS,lenT)) 0 :: ST s (STUArray s (Int,Int) Int) -- 型に注意
  forM_ [1..lenS] $ \i -> do
    let c1 = arrS AU.! i
    forM_ [1..lenT] $ \j -> do
      let c2 = arrT AU.! j
      if c1 == c2 then do
        val <- AM.readArray arrDP (i-1,j-1)
        AM.writeArray arrDP (i,j) (val + 1)
      else do
        val0 <- AM.readArray arrDP (i-1,j)
        val1 <- AM.readArray arrDP (i,j-1)
        AM.writeArray arrDP (i,j) $ max val0 val1
  return arrDP

提出

G - Longest Path

まずはトポロジカルソート順に点を並べる。次にその順に次に行ける点までの距離をDP配列に保存していく。
DP配列の更新時により長い距離を保存していけば良い。

longestPath :: Int -> A.Array Int [Int] -> [Vertex] -> Int
longestPath n g topo = runST $ do
  dp <- newArray (1,n) 0 :: ST s (STUArray s Int Int)

  forM_ topo $ \u -> do
    du <- readArray dp u
    let vs = g A.! u
    forM_ vs $ \v -> do
      dv <- readArray dp v
      writeArray dp v (max dv (du+1))

  maximum <$> mapM (readArray dp) [1..n]

main = do
  [n,m] <- readInts
  es <- replicateM m $ do
    [a,b] <- readInts
    return (a,b)
  let g = buildG (1,n) es
  let vs = topSort g
  let ans = longestPath n g vs
  print ans

提出

H - Grid 1

経路数を合計していくDP。左上から右下へ横方向あるいは縦方向で合計していく考え方は分かりやすい。

modulo = 1_000_000_007

main = do
  [h,w] <- readInts
  as <- replicateM h getLine
  let grid = AU.listArray ((1,1),(h,w)) $ concat as :: AU.UArray (Int,Int) Char
  let ans = solve grid
  print ans

solve :: AU.UArray (Int,Int) Char -> Int
solve grid = runST $ do
  dp0 <- AM.newArray ((1,1),(h,w)) 0 :: ST s (STUArray s (Int,Int) Int)
  AM.writeArray dp0 (1,1) 1
  forM_ (AU.range ((1,1),(h,w))) $ \(i,j) -> do
    val0 <- AM.readArray dp0 (i,j)
    when (i+1 <= h && grid AU.!(i+1,j) == '.') $ do
      val1 <- AM.readArray dp0 (i+1,j)
      AM.writeArray dp0 (i+1,j) ((val1+val0)`mod`modulo)
    when (j+1 <= w && grid AU.!(i,j+1) == '.') $ do
      val2 <- AM.readArray dp0 (i,j+1)
      AM.writeArray dp0 (i,j+1) ((val2+val0)`mod`modulo)
  AM.readArray dp0 (h,w)

  where
    (_,(h,w)) = AU.bounds grid

  print ans

提出

I - Coins

確率DPは自力ではできなかったので解説を見つつ遷移の仕方を学びました。

main = do
  n <- readLn :: IO Int
  ps <- readDoubles
  let arrP = A.listArray (1,n) ps :: A.UArray Int Double
  let ans = solve n arrP
  let m = (n+1) `div` 2
  let ans2 = [v | i <- [1..m], let v = ans A.! (n+1,i)]
  print $ sum ans2

-- DP[i][j] i枚投げた時に表がj枚となる確率
-- DP[i+1][j] += DP[i][j]*pi
-- DP[i+1][j+1] += DP[i][j]*(1-pi)
solve n arrP = runSTUArray $ do
  arrDP <- AM.newArray ((1,1),(n+1,n+1)) 0 :: ST s (STUArray s (Int,Int) Double) -- 型に注意
  AM.writeArray arrDP (1,1) 1.0
  forM_ [1..n] $ \i -> do
    let pi = arrP A.! i
    forM_ [1..n] $ \j -> do
      dp0 <- AM.readArray arrDP (i,j)
      modifyArray' arrDP (i+1,j) (+dp0*pi)
      modifyArray' arrDP (i+1,j+1) (+dp0*(1-pi))
  return arrDP

modifyArray' :: (MArray a e m, Ix i) => a i e -> i -> (e -> e) -> m ()
modifyArray' ary i f = do
  v <- readArray ary i
  let v' = f v
  v' `seq` writeArray ary i v'

提出

おわりに

J以降は難しすぎて今後やるかどうかはグレーです。多分無理かな。。。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?