競技プログラミング
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 の後ろ側に関心があるためです。
- クエリ
1
: A の末尾に値x
を追加する -
クエリ
2
: A の後ろからk
番目の値を求め、標準出力に出す
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 道場』を開発中です。画面レイアウトを組むだけで一苦労しています。