競技プログラミング

訓練

先週の ABC 325 E を upsolve した。 Dijkstra の典型だったが TLE した。人の提出を真似て枝刈りをすると AC になった。

色々なダイクストラ高速化 (yosupo 氏) を見ると、枝刈りによって『何十倍も速くなる問題も』あるという (P15) 。オーダーは変わらないと思っていたが……??

青 diff DP を 3 問解いた。 TEE を見ると約 2 ヶ月の周期性があったので脱したい。

2023-10-29-tee.png
Figure 1: 4 月初旬から 10 月下旬の TEE の推移

AHC 025

AHC 025 が終了した。複数の重りをグループ分けし、重さを平均化せよというインタラクティブ問題だった。

結果は緑パフォだった。個々の要素をソートして、当てずっぽうに組み合わせることしかできなかった。そろそろ対策してみたい。

ABC 326

ABC 326 に参加した。

A 問題 ではセットアップが壊れていて焦った。

B 問題 は算数だった。

C 問題 では尺取り法 (two pointers) のテンプレートを使った。条件を満たす (l, r) 区間の一覧が返るようになっている:

twoPointers :: Int -> ((Int, Int) -> Bool) -> [(Int, Int)]
twoPointers len p = _ {-# 実装は汚いので省略…… #-}

添字バグに戸惑った。閉区間 [l, r] では len = r + 1 - l となるのが厄介だった。

尺取り法は 2 分探索で代替できない場合がある。たとえば 第 8 回 PAST - L は 2 分探索だと TLE になった。

D 問題 は枝刈りができず見送った。実装も大変だったらしい。まだ upsolve していない。

E 問題 は期待値 DP として解いた。図のような遷移において、 \(E_i = \sum_j (E_j + \Delta x_{i, j}) p_{i, j}\) が成り立つ。実際、期待値の定義 \(E[X] = \sum_i x_i p_i\) に立ち返ると計算式を証明できる (省略) 。

2023-10-29-ex-dp.png
Figure 2: 期待値 DP の雰囲気

\(dp[i] := E_i\) はトポロジカル順に求める (終点から順に求める) 。今回の問題における遷移を整理すると \(E_i = \sum_j (E_j + \Delta x_{i, j}) \frac 1 N = (\sum_j E_j + \sum_j \Delta x_{i j}) \frac 1 N\) であり、それぞれの \(\sum\) をセグメント木と累積和から計算できた。セグメント木も累積和にした方が賢い。

確率変数 \(X\) などは雰囲気で書いており、人には伝わらないかも……。

F 問題 は x, y 成分に分けると半分全列挙になるが、経路復元が間に合わなかった。 upsolve したい。

以上、 C 問題 (尺取り法) と E 問題 (期待値 DP) を貯金で乗り切った。

備忘録 (期待値 DP)

EDPC - J Sushi に通じる類題を考える。

1/2 の確率で表が出るコインがある。 3 回表を出すまでにコインを投げる回数の期待値は?

2023-10-29-coin-dp.png

DP 的な式整理無しで解くのは無理な気がする。まず \(E_2 = \sum\limits_i p_i x_i = \sum_\limits{n=1}^{\infty} \frac n {2^n} = 2\) 。 \(E_1\) は……考えたくない。。

読書

Thunder 本

AHC 025 の結果に刺激を受けて、 ゲームで学ぶ探索アルゴリズム実践入門 を読み始めた。

著者: Thunder

Haskell で取り組むため、 random パッケージを見た。 StdGen が乱数のシード (?) で、あるシード値からは常に同じ乱数が生成される (決定的である) 点が AHC 向けだと思った。

Software Design

Software Design 2023年11月号 の Bram 氏 (Vim の作者) の追悼記事を読んだ。人生か……

Haskell

Qiita Conference 2023 Autmun Day2

naoya さんを (オンラインで) 見た!!!!

ツイートしなかったもの

State モナド

naoya さんと言えば、普段の投稿がぶっちぎっていて面白い。キーボード箱買い……棚買い事件はもちろん、 Haskell でも突き抜けている。最近は State モナドの使い方が腑に落ちて面白かった。

一方、そこに State モナドを使って再帰の過程でグローバルな領域に計算結果を書き込むようにする。すると途端に再帰の記述の認知負荷が下がって、楽になる。 pic.twitter.com/y0n64UKXpS

— naoya (@naoya_ito) October 7, 2023

この『いいね』数、みんな付いて来れなかったか……!

State モナドを immutable データに対する ST モナドのように扱えることが示されていて驚いた。むしろ、 STIOState と似ていると考えた方が Haskell 的なのかもしれない。

IO の正体

mod_poppo 氏 unsafePerformIOではじめる愉快なHaskellプログラミング結論 によると、 GHC.IO は以下の newtype であるという:

newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))

ほぼ State モナド (?) 。なるほど『純粋』関数型言語 Haskell だ。

しかしまだ IOState が同じだとは思えない。以下でギャップを埋めてみた。

runIO は無いのか

StateevalState に対して、 IOrunIO は無いのか。これは main:: IO () に対して mainImpl = runIO main RealWorld が隠されていると解釈すれば、 main も通常の Haskell であると言える気がする。たぶん。

可変変数に関して

State ではすべての状態が文脈の中に隠れているのに対して、 IO では ref <- newIORef (0 :: Int) のように識別子が露出する。この違いをどう捉えるべきか。

まず refIO が使用するハンドル?参照?であって、可変変数ではない。 ref 定数である。そして ref ではなく readIORefwriteIORef のような外界 (RealWorld) に触れる操作の方が特別 (mutable) であると考えられる。

-- `IORef` のサンプルコード:
ref <- newIORef (1 :: Int)
x1 <- readIORef ref -- `ref` が参照する値をコピーして読み出す (`1`)
writeIORef ref (x1 + 1) -- 参照先の値を更新
x2 <- readIORef ref -- `ref` が参照する値をコピーして読み出す (`2`)

また State モナドの中にアロケータ相当のデータ (たとえば IntMap Int) を入れれば、 State の文脈で以下のようなコードを書くことも不可能ではない:

ref <- newIntRef (0 :: Int)
x1 <- readIntRef ref
writeIntRef ref (x1 + 1)

よって IO は アロケータ (など) が入った State モナドであるとみなせば、 State と見かけ上の差が無いと言えるはず。

Mutable な primitive 操作

StateIO がほぼ同じものであることは分かった。真の違いは、 writeIORef など IO でのみ許される関数の実装にある。ここでメモリ領域の書き換えを行い、 RealWorld -> RealWorld という幻想を作っているはずだ。

まず writeIORefwriteSTRef を使って実装されていた。 writeSTRef は次の通り writeMutVar# に依存している:

writeSTRef :: STRef s a -> a -> ST s ()
writeSTRef (STRef var#) val = ST $ \s1# ->
    case writeMutVar# var# val s1#      of { s2# ->
    (# s2#, () #) }

そして writeMutVar# の関数定義は base パッケージに無かった。 How are mutable arrays implemented in Haskell? を見ると、 writeMutVar# を始めとした primitive な操作は prim-ops に定義されているらしい。その実装は Haskell ではない。

まとめ

IOState と同じく純粋な Haskell として定義されているが、ランタイム中の?非純粋な関数 (prim-ops) に触れられる点が特別であったと認識できた。

不正確な理解だが、今はこれでヨシ!

Misc

Haskell 本が書きたい

Haskell で茶色コーダーになるための本が無い。もちろん素晴らしい本は存在するけれども、各自の試行錯誤と照らし合わせるには分量が足りない。あるいは競プロにフォーカスしていない。

よってこの間を埋める投稿がしたい。 Advent calendar に間に合うだろうか。

表紙

表紙画像を組んでみた: サンプル (3 ヶ月で削除) 。グロ注意。アウト過ぎたので、別の案を外注してみた。僅か 6,000 円?! 待機。

『ラムダスカル』というキャラクターを考えた。ふと Haskell のキャラクターを探してみると、これが出てきた。さすが tanakh 氏。いつ見ても先を行く人だ。

Haskellのキャラクター作った pic.twitter.com/fu1jX2yKMP

— Hideyuki Tanaka (@tanakh) May 21, 2014