競技プログラミング

ABC 340

ABC 340 に参加しました。

A 問題

数列 \(a, a + d, .., b\) を生成します。リストの range syntax を使ったり、 iterate を使うとシンプルに書けるようです:

[a, a + d .. b]
takeWhile (<= b) $ iterate (+ d) a

僕は unfoldr を使ってしまいました。

B 問題

クエリ処理の問題です。 Haskeller にとっては複数の厄介事が絡み合うため、茶 diff 相当かもしれません。

クエリ処理の方針

スタックを使います。いずれのクエリでも文字列 A の後ろ側に関心があるためです。

Haskell においてはリストがスタックとして使えます。

解答の出力

クエリ 2 においては、求めた値を標準出力に出す必要があります。クエリ 2 が来る度に都度 print してしまうのが簡単です。ただしクエリ処理の関数には IO モナドが必要です。

都度 print する解答
main :: IO ()
main = do
  !q <- ints1
  !qs <- U.replicateM q ints2

  let f :: [Int] -> (Int, Int) -> IO [Int]
      f !acc (1, !x) = return (x : acc)
      f !acc (2, !k) = do
        print $ acc !! pred k
        return acc
      f _ _ = error "unreachable"

  U.foldM'_ f [] qs

より Haskell らしい解答としては、クエリ処理を純粋関数とします。戻り値を答えの一覧とし、最後に print します。 unfoldr などが適切な関数だと思います。

unfoldr による解答
main :: IO ()
main = do
  !q <- ints1
  !qs0 <- U.replicateM q ints2

  let !res = U.unfoldr f ([], qs0)
        where
          f (!acc, !qs) = case U.uncons qs of
            Nothing -> Nothing
            Just ((1, !x), qs') -> f (x : acc, qs')
            Just ((2, !k), qs') -> Just (acc !! pred k, (acc, qs'))
            _ -> error "unreachable"

  U.forM_ res print

答えの一覧をリストに溜めながら foldl' して、最後に reverse するのも OK です。計算量のオーダーは \(O(N)\) のまま変わりません。競技プログラミングにおいては reverse は実質無料です。

C 問題

\(n\) を何度も半分へ割っていきます。 \(n \le 10^{17}\) より大きな \(n\) が与えられる場合に注意します。 解説 にある通り、計算結果のキャッシュを持つ (メモ化する) と考察を放棄できます。

ここではメモ化に IntMap を使用します。素直に書くとこうなりました:

solve :: Int -> IM.IntMap Int -> (Int, IM.IntMap Int)
solve !x !im0 = case IM.lookup x im0 of
  Just !cost -> (cost, im0)
  Nothing -> (cost', IM.insert x cost' im')
    where
      !x1 = x `div` 2
      !x2 = x - x1
      (!cost', !im') =
        let (!cost1, !im1) = solve x1 im0
            (!cost2, !im2) = solve x2 im1
         in (x + cost1 + cost2, im2)

State モナドを使うとこうなりました:

solve :: Int -> IM.IntMap Int -> (Int, IM.IntMap Int)
solve !x !im0 = case IM.lookup x im0 of
  Just !cost -> (cost, im0)
  Nothing -> (cost', IM.insert x cost' im')
    where
      !x1 = x `div` 2
      !x2 = x - x1
-      (!cost', !im') =
-        let (!cost1, !im1) = solve x1 im0
-            (!cost2, !im2) = solve x2 im1
-         in (x + cost1 + cost2, im2)
+      (!cost', !im') = (`runState` im0) $ do
+        !cost1 <- state $ solve x1
+        !cost2 <- state $ solve x2
+        return $ x + cost1 + cost2

もっとシュッとした解答の人も多くてさすがです。それがしの提出はスパゲッティゆえに……

D 問題

D 問題は Dijkstra 法 (辺に重みがある場合の BFS) をやるだけのようです。

E 問題

遅延セグメント木を使うと区間 add ができます。緑 diff とは……

遅延セグメント木のはずがないと、メタ読みで F, G に行ってしまいました。メタを外してくるのは面白いですね。

F 問題

解けませんでした。平行四辺形の面積は外積の長さですから、三角形の面積はその半分です。式を整理すると、拡張ユークリッドの互除法で解けるそうです。

外積は行列式を使って表現できます。手計算の際には、行列をループさせると余因子の符号を考えなくて済みます。

\[ \begin{aligned} \mathbb{U} \times \mathbb{V} &= \begin{vmatrix} \mathbb{U} \mathbb{V} \mathbb{E} \end{vmatrix}^{t} \\ &= \begin{vmatrix} u_{1} & u_{2} & u_{3}\\ v_{1} & v_{2} & v_{3}\\ \mathbb{e}_{x} & \mathbb{e}_{y} & \mathbb{e}_{z} \end{vmatrix} \\ & \{3 行目に関する余因子展開\} \\ &= \mathbb{e}_{x} \begin{vmatrix} u_{2} & u_{3} \\ v_{2} & v_{3} \end{vmatrix} + \mathbb{e}_{y} \begin{vmatrix} u_{3} & u_{1} \\ v_{3} & v_{1} \end{vmatrix} + \mathbb{e}_{z} \begin{vmatrix} u_{1} & u_{2} \\ v_{1} & v_{2} \end{vmatrix} \\ &= \mathbb{e_x} (u_2 v_3 - u_3 v_2) + \mathbb{e_y} (u_3 v_1 - u_1 v_3) + \mathbb{e_z} (u_1 v_2 - u_2 v_1) \\ & \{2 次元ベクトルの場合 (u_3 = v_3 = 0)\} \\ &= \mathbb{e_z} (u_1 v_2 - u_2 v_1) \end{aligned} \\ \]

今回の計算においては \(\mathbb{U} = \mathbb{A}, \mathbb{V} = \mathbb{X}\) を代入します。三角形の面積が 1 に等しい条件は、以下の等式が成り立つことです。

\[ \begin{aligned} \mathbb{A} &:= \begin{bmatrix} A\\B\\0 \end{bmatrix}, \mathbb{X} := \begin{bmatrix} x\\y\\0 \end{bmatrix} \\ \frac {|\mathbb{A} \times \mathbb{X}|} {2} &= 1 \iff |Ay - Bx| = 2 \iff Ay - Bx = \pm2 \end{aligned} \\ \]

この等式を満たす整数値 \(x, y\) は拡張ユークリッドの互助法によって求まります。実装は @4tsuzuru さんの exgcd 関数を拝借しています。

TODO: \(x, y\) の変域の考察

拡張ユークリッドの互除法

exgcd が活躍する具体例は、算数でよくあるバケツで水を組む問題です。容量 \(A\) のバケツと容量 \(B\) のバケツを使って、お風呂にちょうど \(L\) リットルの水を汲みます。

互いに疎な整数 \(A, B\) に対して \(n_A A + n_B B = 1\) を満たす整数 \(n_A, n_B\) の組が存在します。 exgcd によって \(n_A, n_B\) を求めると、両辺を \(L\) 倍して任意の \(L \mathrm[L]\) の水を汲むことができます。

類題は ABC 186 - E でした。フレンズさん回で exgcd は頻出ですね。

Misc

キーボード探し

MiniAxe の入荷を待っています。 3 行 5 列 + 親指キーの分割キーボードです。タッチデバイスと Bluetooth が付属しているとなお良いのですが……。

React 入門

『Emmet 道場』を開発中です。画面レイアウトを組むだけで一苦労しています。