Closed249

競プロ盆栽 (Haskell 盆栽 2 → 入緑)

toyboot4etoyboot4e

Haskell の気づきメモ

NPlusKPatterns は危ない

これは数値にマッチするパタンを (n + 1) のように書くことができる拡張です。 1-based index を 0-based index に変換するときなどに使用しました。しかし n が負の数になる時はエラーが発生します。

N + K パタンが危険な例

言語拡張を有効にして ghci を起動します:

$ ghci -XNPlusKPatterns
Prelude> 

一見 N + K パタンは問題なく動きます:

Prelude> let x = 1
Prelude> let (n + 1) = x
Prelude> n
0

しかし n が負の数になるとエラーが発生します:

Prelude> let x = 0
Prelude> let (n + 1) = x
Prelude> n
*** Exception: <interactive>:2:5-15: Non-exhaustive patterns in n+1

このように罠がある以上、多少冗長でも letwhere を書こうと思いました。 詳細 (未確認)

2 次元の可変配列には vector よりも array の方が向いているかもしれない

2 次元の不変配列には V.Vector (VU.Vector Int) のようなものが利用できます。ダブルポインタの発生は懸念されますが、そこまでパフォーマンスに影響は出ません。また各行の長さが違っても良い点はメリットになる時もあります。

2 次元の mutable vector が欲しかったのですが、 VU.create が使えず困りました。 State thread の表現力は予想以上です。

Vector で 2 次元配列を扱うならば、 (1, 2) のような 2 次元の添字を 1 次元の添字に変換すれば良いと考えました。たとえばグリッドの横幅を w として、 index = x + y * w のような式が考えられます。

実は、このような添字の汎用的な表現が型クラス Ix であると気付くことができました。また arrayIx を中心としたパッケージであると理解できました。

2 次元配列の添字の使い方 (array / Ix)

たとえば row-major な行列のリストを array の中に保存するなら、 newListArray ((0, 0), ((h - 1, w - 1)) list で array を生成し、添字 (y, x) でアクセスします。添字を (row, column) と見れば、 2 次元の array は row-major な行列であるとも解釈できます。

添字 (a, b) と { x, y } の対応について

Ix を試してみます。 Ix や array は標準?に入っているため、特に ghci にオプションは要りません:

$ ghci
Prelude> import Data.Ix
Prelude Data.Ix>

Ix クラスの index 関数は、添字範囲 [x1, x2] に対する相対位置を返します:

Prelude Data.Ix> index (0, 4) 2
2
Prelude Data.Ix> index (2, 6) 2
0

これが n 次元に拡張されており、 2 次元の添字も使用できます:

Prelude Data.Ix> index ((0, 0), (5, 5)) (0, 1)
1
Prelude Data.Ix> index ((0, 0), (5, 5)) (1, 0)
6
Prelude Data.Ix> index ((0, 0), (5, 5)) (5, 5)
36

注意すべきなのは、タプルの右端の値が 1 次元目を表すということです。 Row-major な行列の場合は、タプルの 2 番目の要素を column とし、生成時のサイズは (h, w) にする必要があります。

遅延評価で DP ができるらしい?

https://mobile.twitter.com/naoya_ito/status/1590483420404383745

map f xs の引数順は関数合成で便利

F# ではパイプライン演算子? が使用されますが、 Haskell では関数合成が好まれる気がします。 map のシグネチャにもそれは表れているなと改めて思いました。

toyboot4etoyboot4e

漠然とした興味

手続き型 Haskell は汚くてしんどい、純粋な Haskell は思いつくのが難しい

いずれにせよ、 Haskell は読むのも書くのも難しいと思います。

モナドのランク?

失敗しないモナドや、箱より文脈に近いモナドなど、何らかの違いを感じます。 State なんかは値というよりも関数のモナドですし。

toyboot4etoyboot4e

ABC ノート

面白い問題が多いと気づいてきました。

  • ABC 274 D: (IntMap を使って) 重複を消去すれば、計算量を O(2^N) から空間サイズ程度まで削減できる。基本中の基本みたいな問題です (死亡) 。

  • ABC 275 C: ある種の全探索で十分でした。つらい。。

  • ABC 275 D: Sparse なメモを使います。 274 D の経験後なら解けるべきだった (死亡) 。

  • ABC 277 C: DFS, union-find + 座標圧縮 (からの全探索) 、 sparse な union-find などの解答があります。良問な気がします (死亡) 。

  • ABC 278 D: 配列のゼロクリアは O(N) もしくは O(log N) 程度みたいです。実は生成の replicate も O(N) みたい 。今回はゼロクリアを O(1) で実現するため、世代番号を使う必要がありました。無事死亡……。
    言語拡張 TupleSections により \x -> (x, 1)(, 1) と略して書くことができます。 Vector をタプルにマップするときなどに使えますね。

  • ABC 278 E: 考察で計算量を減らすことができず死亡。。あとタプルの unboxed array は作ることはできないので気をつけます。 これを Haskell で実装するのに 6 時間もかかったよ…… 。これが AtCoder を 実装 > 考察 に変えるハックです。

toyboot4etoyboot4e

その他ネタになりそうな話題

レートは実力の対数のようなものらしい?

喩えだとは思いますが、もしも水色 (レート 1,200) を目指すなら今 (レート 400 弱) より約 10 倍強くなる必要があります。うーむ (普通にやっていたら無理では……?) 。

1 問解くとレートが 1 上がるという説もあり、逆に実力とは……?

AtCoder Problems を問題一覧にすると良かった

AtCoder Problems (使い方) では、コンテストを跨いだ問題一覧が見れます。解答状況も見えて、たとえば 僕の場合はこちら 。いずれレコメンド機能やバーチャルコンテストも利用する時が来るかもしれません。

流石に鉄則本には対応していないようですね。自分でカウントします。

レートのシミュレータ で目標のパフォーマンスが分かる

これで目標設定が可能に……? とはいえパフォーマンス値に勘が働きませんし、問題の色を見れば良い気はします。問題の色 = その色の参加者の 50% が解けた問題……だったはずなので、目標のレートより少し下の問題を安定して解ければ良いはずです。まず茶色を目指すなら、灰色後半の問題が解ければ良いはず?

言語サーバが動いていない気がするんだけど!

HLS 1.5.1 だからだと思っていたけれど、さすがにフォーマットしかできない言語サーバが 1.5.1 なんて高いバージョンのわけもないよね……

toyboot4etoyboot4e

HLS が動いてなかった

言語サーバは重要です。エラー表示したり、 変数の型を表示したり 、コードアクションで型ノーテーションを補完したり……。あまりの有用性に涙が溢れます。

今まで言語サーバ無しで Haskell を書いていたのはある意味凄くないですか? (泣)

原因

現在のファイル構成は以下です。この状態で Emacs を起動すると、 LSP のプロジェクトルートが abc-hs になっていました:

abc-hs
├── abc-277
└── abc-278
    ├── a
    │   ├── Main.hs
    │   └── test-cases
    ├── b
    └── g

対処方法 (暫定)

abc-* 以下に .projectile ファイルを置きます (Emacs の場合) 。これで ahc-* が LSP の workspace フォルダになります。他のエディタの場合は……最悪 .git を作ります?

また abc-* 以下で package.yaml を作り、実行ファイルごとに executables を書いて gen-hie (implicit-hie) で hie.yaml を作ります (Stack を使っている場合) 。これ自動化できないの……?

何とかしたいこと

  • プロジェクトごとに GHC や HLS のバージョンを設定したい
  • package.yaml で 1 つ 1 つ実行ファイルを列挙する手間を省きたい  (自動生成できる? cabal だったら楽だったりする?)
toyboot4etoyboot4e

interact を使った宣言的な解答

Haskell で鉄則本を解く Youtube が現れました。 第一問の解答 の時点で、目を見張るほど宣言的です。これはかっちょいい。

YouTubeのvideoIDが不正ですhttps://www.youtube.com/playlist?list=PLmilE0iwpTVuBPYT5ZRuVGaf_PXUofwDk

interact について

解答を引用します:

module Main where

main :: IO ()
main = interact (encode . solve . decode)
 
decode :: String -> [[Int]]
decode =  map (map read . words) . lines
 
encode :: [[Int]] -> String
encode = unlines . map (unwords . map show)

solve :: [[Int]] -> [[Int]]
solve dds = case dds of
    [n]:_ -> [[n * n]]

interact を使えば、純粋な関数で解答を作ることができます。強制的な ByteString 縛りにすれば練習にもなりそうです。

interact :: (String -> String) -> IO ()
interact :: (ByteString -> ByteString) -> IO ()
interact :: (Text -> Text) -> IO ()

……と思いきや?

入力が複雑な場合は 、 decode (あるいは parse) の返値はタプルになるのでしょうか。あるいは solve . decode を 1 つの関数にしてしまえば良いですね:

main :: IO ()
main = BS.interact solve

solve :: BS.ByteString -> BS.ByteString
solve = ..

solve の中でも IO を使いたいとなれば:

main :: IO ()
main = BS.interact solve

solve :: BS.ByteString -> IO BS.ByteString
solve = ..

おや、 main 関数を書くのと大差無いかもしれません。改行文字の連結も大変そうですし。うーむ

toyboot4etoyboot4e

direnv で Haskell 環境切り替え

AtCoder では GHC 8.8.3 / HLS 1.5.1 を使いますが、他のプロジェクトでは最新の Haskell を使いたいものです。 cabal で環境を変えるというのがあるそうですが、アドベントカレンダーで記事が出るなら自分から調べに行くのもなぁと思います。

代わりに direnv でプロジェクト毎に環境変数が切り替わるようにすれば、 Haskell の環境を切り替えられるかも?

インストール

direnv 公式サイト通りインストールして、シェルのフックを設定します。

$ curl -sfL https://direnv.net/install.sh | bash

~/.bashrc:

if command -v "direnv" > /dev/null ; then
    eval "$(direnv hook bash)"
fi

~/.config/fish/config.sh:

if command -sq direnv
    direnv hook fish | source
end

バージョン切り替え

$ direnv edit .
$ direnv allow
.envrc
PATH_add ~/.ghcup/ghc/9.2.4/bin
PATH_add ~/.ghcup/hls/1.8.0.0/bin
~/d/h/rogue-hs ❯❯❯ which ghc
/Users/tbm/.ghcup/ghc/9.2.4/bin/ghc

~/d/h/rogue-hs ❯❯❯ cd ../
direnv: unloading

~/d/hs ❯❯❯ which ghc
/Users/tbm/.ghcup/bin/ghc
direnv: loading ~/dev/hs/rogue-hs/.envrc
direnv: export ~PATH ~XPC_SERVICE_NAME

~/d/h/rogue-hs ❯❯❯ which ghc
/Users/tbm/.ghcup/ghc/9.2.4/bin/ghc

これで動いた?

ビルドが長いのでまた明日〜

30 分後

完璧に動きました。バージョンの異なる GHC を使った 2 つのプロジェクトで同時にエディタを立ち上げることができます。やったぜ。

stackcabal は特に切り替える必要がありませんでした。色々やりたいことはあるので遊んでいきます。

toyboot4etoyboot4e

[印刷用問題文]

『問題』ページ下部のボタンをクリックして、 競プロ典型 90 問競技プログラミングの鉄則 演習問題集 の PDF をゲット?! iPad に入れておきます。

iPad よりも高いけれど Kindle Scribe が欲しい

典型 90 問の解答は Twitter にある他、 リポジトリ に画像がまとまっているのでこれも PDF にしました。プリンタがあれば印刷するのですが。

toyboot4etoyboot4e

State モナドが分かってきたぞ!

State モナドで mapAccumL

mapAccumL は状態を持ちながら map する関数です:

#!/usr/bin/env stack

import Control.Monad.State
import Data.List

main :: IO ()
main = do
  print $ mapAccumL (\acc x -> (acc + x, x)) (0 :: Int) [1, 2, 3]
  -- (6, [1,2,3])

この関数は Traversable なデータ型にしか実行できませんから、 Vector には実行できません。ただし Vector にも mapM は存在するので、 mapMState モナドを使って mapAccumL と同等な計算を作れば Vector に対しても実行できます。

State モナドと do 記法を使って、なんとか mapAccumL を実装できました:

汚い実装
#!/usr/bin/env stack

import Control.Monad.State
import Data.List
import Data.Tuple

myMapAccumL :: (Int -> Int -> (Int, Int)) -> Int -> [Int] -> (Int, [Int])
myMapAccumL f s0 xs =
  swap
    $ runState
      ( mapM
          ( \x -> do
              acc <- get
              let (acc', x') = f acc x
              put acc'
              return x'
          )
          xs
      )
      s0

main :: IO ()
main = do
  print $ mapAccumL (\acc x -> (acc + x, x)) (0 :: Int) [1, 2, 3]
  -- (6, [1,2,3])

  print $ myMapAccumL (\acc x -> (acc + x, x)) (0 :: Int) [1, 2, 3]
  -- (6, [1,2,3])

これの無名関数の部分が Int -> State (s -> (Int, s)) という型であることを考えると、 mapM は各要素を State に写し、移された State の入出力を連結するものであることが分かり:

#!/usr/bin/env stack

import Control.Monad.State
import Data.List

main :: IO ()
main = do
  print $ runState (mapM (\x -> state $ \acc -> (x, x + acc)) [1, 2, 3]) (0 :: Int)
  -- ([1,2,3], 6)

これでたとえば累積和の計算ができます:

  -- 数列 → 累積和
  print $ runState (mapM (\x -> state $ \acc -> (x + acc, x + acc)) [1, 2, 3]) (0 :: Int)
  -- ([1,3,6], 6)

  -- 累積和 → 数列
  print $ runState (mapM (\x -> state $ \last -> (x - last, x)) [1, 3, 6]) (0 :: Int)
  -- ([1, 2, 3], 6)

  -- evalState = fst . runState

Vector には mapAccumL を実行できませんが、 State モナドを使った mapAccumLVector 版の mapM を使って実行可能です。

必要だった知識・理解

  • States -> (a, s) の wrapper
    • state で純粋関数を State にできる
    • runStateState に実引数を与えられる
  • mapM の過程は [a] -> [m a] -> m [a] という変換のイメージで、 sequence . map という 2 段階に分けて考えると、それぞれの要素を State モナドに写し、それらの入出力を繋ぎ合わせて全体で 1 つの State モナドにするもの

なぜ混乱したのか

脳みそが弱いからとしか言いようが無い。。

  • do 記法が記憶に焼き付いた
  • State を単なる関数として考えることを忘れ、ブラックボックスとして扱おうとした
  • そもそも fmap mapMmod_poppo 氏のブログ記事 から与えられた発想であることを忘れ、その意味を考えていなかった

何とかコンパイルできるコードを作り、質問して state 関数を教えてもらったことが取っ掛かりになりました。誰も僕の脳みそをデバッグできない。。こういう馬鹿さはずば抜けている気がします。でも自力で脱出するように上手くリードしてもらえると、意外になんとかなりますね。

まあなんとかやっていきます。。

toyboot4etoyboot4e

逆に do 記法の State に関しては、 do 記法がポイントフリースタイルなモナディック関数のチェインであることを思い出じて理解できました。

toyboot4etoyboot4e

部分適用とポイントフリースタイル

Pointless style と揶揄されることもあるポイントフリースタイルですが、 無名関数を in-place で書いたり関数合成が続く場合には非常に良いと思えてきました。久しぶりに Rust を書くと .map(|x| ..) という形が無念です。

構文

  • if は関数ではない (if' は Prelude にはない)
  • `f` で関数を演算子として使うことができる
  • 演算子の部分適用 (セクション) の (a `f`)(`f` b) は区別される
  • (op) で演算子を関数として使うことができる

Prelude

  • id :: a -> a
  • const :: a -> (b -> a)
  • flip :: (a -> b -> c) -> (b -> a -> c)
  • succ, pred
  • curry, uncurry

Wiki より

  • Point とは空間上の点 (値) のことであり、関数が適用される値のこと
  • つまり pointfree とは引数を省いた表記法
  • Haskell の構文を pointfree にしたり元に戻す関数がある (pl, unpl) 。これを使って 様々なポイントフリースタイルのコンビネータ が発見された。必ずしも人間が見つけたわけでは無いのか。
    • owl: ((.)$(.))
    • dot: ((.).(.))
    • swing: flip . (. flip id)
    • squish: f >>= a . b . c =<< g

.$ を関数として使うのが分かりません。 owl はコードゴルフで見た気がする? 今読む気はしませんが、この辺も使いこなしていきたいところです。

toyboot4etoyboot4e

関数の applicative, 引数の分身

\[a, b] -> (a, b)

<*> で入力を分身させて、出来上がった……アプリカティブに……? <$> で関数を適用します (本当の仕組みは知りません):

Prelude> (,) <$> (!! 0) <*> (!! 1) $ [10, 11]
(10,11)

……いや (\[a, b] -> (a, b)) を書いた方が良さそうです。

toyboot4etoyboot4e

do 無しで書こう

bind を無くす

before

do
  v1 <- readArray tbl (i2, i1 - 1)
  v2 <- readArray tbl (i2 - 1, i1)
  return $ max v1 v2

after

max <$> readArray tbl (i2, i1 - 1) <*> readArray tbl (i2 - 1, i1)

IO も applicative!

追記

『Applicatve スタイルを使おう』の方が正しいですね。

toyboot4etoyboot4e

短い変数名もイケてる気がする

line ではなく ln, table ではなく tbl, step よりも f がイケている気がします。

などと書いていましたが、やはり慣習に従ったほうが良い気がして来ました。 initialLine よりも line0.

もっとも頭文字を取って error を e と書くような極端な省略は苦手です。 Go などに触れると適応できるかもしれません。

toyboot4etoyboot4e

変数名の重複を防ぐ

主にナップサック問題から。

  • ローカル変数の名前が一時変数と被らないようにする (main)
    Haskell には shadowing が非推奨なので、変数名が被らないように配慮すべきです。 n ではなく nItems などと命名して名前の重複を避けられます。

  • 同じ型のデータが 2 つある場合、 xy などの prefix を付ける
    xs@(x:xrest) ys@(y:yrest)のようなパタンマッチを利用します。

  • あえてパタンを分解しない手もある
    上の例で、さらに x を分解して x@(xw, xv) などと書くこともできますが、 fst x, snd x でも事足ります。

toyboot4etoyboot4e

Stack script を :load するには (→ stack repl を使う)

デバッグのため REPL から stack script の関数を呼びたいとします。

ghci から stack script を :load すると、コメントを無視してしまうため Stackage のパッケージが読めません。かわりに stack repl の起動時にプロジェクトを選びます。もしくは stack repl a/Main.hs のように読み込むファイルを指定します。

toyboot4etoyboot4e

Stack script をやめよう

Stack script を使っていたモチベーションは、環境構築が楽だった (oj のテストコマンドを ./Main.hs にできた) ことでした。 package.yaml もちゃんと書くことにしましたし、普通に stack でビルド・実行した方が良い気がします。

さて stack build abc279:exe:a-exe を試してみましたが、 a, b, c .. とすべてコンパイルされてしまいます。実行ファイル 1 つだけコンパイルしたいのですが……。

TODO

toyboot4etoyboot4e

普通に GHC でビルドすればいいのか……! パッケージをどこから取ってくるのか謎ですが。

toyboot4etoyboot4e

Misc

再帰やリスト内包表記を使わなくなってきた

Vector には基本パタンマッチができませんし、大体 fold で片付いてしまいます。 fold なら他の言語でも再現が楽です。

Rust で fold したことはないかも。

計算が派生する場合 (DFS など) は再帰関数を書いています。ファイルツリーを下っていくときなども再帰になりそう。つまり単純なループは foldl で表現できるし、新たな入力を取り寄せるためには再帰が必要なのでしょう。

forM_ 内で print するか、文字列を作って print するか

後者の方が美しいです。パフォーマンス的には……?

IntMap は確かに State モナドと相性が良さそう

do 記法は各行をポイントフリースタイルに変えてしまうから、 State モナドの do 記法によって IntMap の受け渡しをポイントフリースタイルにするのは自然に見えました。パフォーマンスはどうでしょう。

https://gist.github.com/wuerges/d84f9df6fb57789e9ba9828ccdf66953

Stream って直に触れるのだろうか (あるいは Iterator はある?)

Rust なら xs.iter().chain(iter::once(x)) のようにチェインができます。同じことを Haskell でやるには?

fold に限っては fold step x xs `step` x' のように書けます。

toyboot4etoyboot4e

茶色パフォ後半が出るようになってきたけれど

解ける問題数は 3 から増えていません。

提出速度

高速で解答できると、同じスコアの参加者の中で順位が上がるため、パフォーマンスも上がるというだけです。つまり Vimmer であるかが問われます (違う)

水色ライン

順位 1,000 でパフォーマンス 1,300 程度でした。 5 問完答で 30 分弱、この辺が水色到達のボーダーラインな気がします。

レーティングの上昇速度

現在のレーティングの倍のパフォーマンスを出しても、レーティングは 11/10 程度にしかなりませんでした。さっさと水色に到達するには、 6 問解くしかありません。無理では……?

緑色目指して

正しくは 4 完目指して。

  • 動的計画法
  • グラフ (DFS, BFS, 連結グラフでグループ分け)
  • Mod 計算
  • 素因数分解、最大公約数、最小公倍数など
toyboot4etoyboot4e

遅延評価で動的計画法

ズルいけれども動きます。また再帰ケース部を let の中の関数や where 句の関数に分けるとより簡単です:

let dp =
      array (0, n) $
        (0, 1) : [(i, acc `rem` unit) | i <- [1 .. n], let acc = step i]
    step i
      | i >= l = dp ! (i - 1) + dp ! (i - l)
      | otherwise = dp ! (i - 1)

print $ dp ! n
toyboot4etoyboot4e

tabulate

Algorithm Design with Haskell より tabulate 関数:

tabulate :: Ix i => (i -> e) -> (i, i) -> Array i e
tabulate f bounds = array bounds [(x, f x) | x <- range bounds]

これで上記を書き直すと:

let dp = tabulate step (1, n)
    step :: Int -> Int
    step 1 = 0
    step 2 = dp ! 1 + xs VU.! 0
    step i = (dp ! (i - 1) + xs VU.! (i - 2)) `min` (dp ! (i - 2) + ys VU.! (i - 3))

ガードが増えて重くなる……かと思いきや速くなりました。うーむ……積極的に使っていきましょうか。

toyboot4etoyboot4e

茶色後半までは解けるようになった気がする

とはいえ問題を難しくすると太刀打ちできません。まだまだ閉塞感が強く、 ABC が趣味と言える日は遠いようです。

今までも同じことを繰り返して来たのかもしれない

新しい表記法であれやこれやを書き散らかすのは、英語も数学も物理も同じですね。

どこまで行くのか

あと何回脳みそで発芽したような感覚を味わえば、 4 問 5 問と ABC を解いていけるのか。あるいは ARC やらヒューリスティックコンテストに挑むことができるのか。どこまで行けるか分かりませんが、経過を面白く思っています。

まだしばらくは這いつくばって行くしかないと思います。案外一生そんなものかな……

toyboot4etoyboot4e

ヒューリスティックの本が出るとか

ポチった! 有名な人みたいですが、誰の本なんだろう!

僕もビーム飛ばしてみたいですね。何のことかさっぱりですが……

https://www.amazon.co.jp/dp/4297133601/

toyboot4etoyboot4e

fold で 2 次元の動的計画法

方針

動的計画法の肝は枝刈りだと思います。入力を 1 つずつ受け取って探索し、テーブル上の『重複』を削除することで計算量をテーブルサイズ程度に押さえます。

Tips

foldscan に変えたら、テーブルとして print できます。また print する都合上、横列を次の列に写す関数を使って fold すると捉えます。

演習

  • 鉄則 A 18. 部分和問題

    • テーブルの形: 横軸は可能な値の組 (Vector Bool もしくは IntSet), 縦軸は入力値
    • 重複の削除方法: bool 値への和にする
  • 鉄則 A 19. ナップサック問題

    • テーブルの形: 横軸は可能な重さの組 (Vector Int), 縦軸は入力値
    • 重複の削除方法: より価値が大きい方を選ぶ
  • 鉄則 A 20. 最長部分列 (連続性を問わない)

    • テーブルの形: 横軸は入力値、縦軸も入力値
    • 重複の削除方法: より長い方を選ぶ

感想

入力が 2 種類だと fold するのはなかなかキツいです。それにせっかく純粋な解答を作っても、 IO / ST を使った方が速いんだ〜〜

IO / ST を綺麗に書けるようにするのも重要かもしれません。 tabulateIO / ST 用に書き換えてみようかな

toyboot4etoyboot4e

ナップサック問題をリストで解く場合

つまり探索をリストで持つ場合、 (w, v) のペアが w, v 共に単調増加になるよう調整します。重さが増えて価値が下がった探索は捨てなければ TLE となります。

不要な探索を捨てた場合、捨てなかった場合

明らかに実行速度に差がありますし、保持する探索の数にも大きな違いが現れました:

  • W = 985042904
  • length state == 3512979 (不要な探索を捨てなかった場合)
  • length state == 57 (不要な探索を捨てた場合)

それでも スキャン数 < W のはずなのですが、なぜ TLE になるのか……?

toyboot4etoyboot4e

IO / ST で 2D 動的計画法 (ナップサック問題)

tabulateIO / ST 版を作ればいいじゃない!

パフォーマンス面の確認

  • Array (AC) は UArray (AC) よりも遥かに悪い
    • メモリ: 83 MB → 597MB
    • 実行時間: 174 ms -> 2466 ms
  • ループ部分を関数に切り分けると (つまり高階関数を使おうとすると?) さらにメモリ使用量が倍近くなる (MLE)
    • ※ この tabulateIO 関数はまだまだ改良が必要

Haskell でナップサック問題を解く場合は、むしろ fold が安牌という気がしますね……

非インテリの所業

tabulateST を作りたいのですが、色々ハマって質問しています。全然課金して質問したいのですが、いつまでも taker のままで良いのでしょうか……

意外と Haskell の限界に近いのかもしれない

STUArray 版が通る ようにしてもらいました! やや無念は残り、

  • 型引数 e を具体的な Int に置き換えています。
  • ユーザ関数 (f) の型推論ができませんでした

毎回 runSTUArray とか forM (range bounds_) $ \i -> do .. とかを書く方が現実的な気がしてきました。 tabulateST を使う案は棄却します。

tabulateST では INLINE 化しても意味が無かった

なんでだろう……?

toyboot4etoyboot4e

メモ化で 2D 動的計画法 (ナップサック問題)

tabulateMap 版を作ればいいじゃない!

Unboxed 版は当然 MLE

まず Map を使う と MLE 。 tabulateST より使い勝手は良いけれど……

IntMap ならギリギリ AC

あら意外……! AC です 。メモリ使用量は STUArray 版と変わらず、実行速度は明らかに遅いという程度。 TLE の危機はありますが、低空飛行で実戦投入できそうです。

また IxMap は無いので IntMap を使うのは少し面倒です。 index bounds_ i のようにして IntMap の引数に変換します。

index を忘れるとコンパイルエラーになるので、そこまで問題ではありません。

ただ、メモを sparse にする意味は無い

パフォーマンス的にも tabulateST の劣化版なのだなぁ……。

HashMap (unordered-container) では TLE

Strict 版なので MLE は避けられましたが、 時間制限を超えました 。妥当なところですね。 IntMap が TLE にならなかった方が意外でした。

toyboot4etoyboot4e

型クラス Ix が便利でいいなぁ

n 次元座標の表現はもちろん、区間の表現もできることを知りました。 Prelude は総洗いした方が良いかもしれない……

$ ghci
> import Data.Ix

index

(min, max) 区間における相対位置を返します:

> index ((1, 1), (2, 4)) (1, 1)
0
> index ((1, 1), (2, 4)) (1, 2)
1

Row-major な行列では、添字は (y, x), サイズは (h, w) のように書きます。

range

n 次元のイテレーションもお手のもの:

> range ((0, 0), (2, 4))
[(0,0),(0,1),(0,2),(0,3),(0,4),(1,0),(1,1),(1,2),(1,3),(1,4),(2,0),(2,1),(2,2),(2,3),(2,4)]

inRange

Inclusive range 内にデータがあるかを判定:

> inRange (1, 5) 3
True
> inRange (1, 5) 5
True
> inRange (1, 5) 6
False

> rangeSize ((0, 0), (2, 2))
9
toyboot4etoyboot4e

そう書けばいいのか

unless

モナディックアクション? では if の代わりに when / unless を使うとスッキリします:

forM_ [1 .. (n - 1)] $ \i -> do
  v0 <- VUM.read vec i
  unless (v0 == -1) $ do
     VUM.modify vec (max (v0 + 100)) (x100 VU.! pred i)
     VUM.modify vec (max (v0 + 150)) (x150 VU.! pred i)

なぜ未だにこんなことを……

listArray @UArray

@ で部分的に型を書くと、全体を推論してくれる場合があります:

input <- concat <$> replicateM n getLineIntList
let mat = listArray @UArray ((0, 0), (m - 1, n - 1)) input
-- let mat = listArray @UArray ((0, 0), (m - 1, n - 1)) input :: UArray (Int, Int) Int

newArray でも同様に:

  explored <- newArray @IOUArray ((0, 0), (h - 1, w - 1)) False
toyboot4etoyboot4e

letwhere モドキ

特に fold するときは、トップレベル関数を作るよりもクロージャを作ってしまいます。

let の中でサブ関数を定義する

let xs = foldl' step s0 [0 :: Int ..]
    s0 = _
    step = _

let の中でサブ関数を定義する + 本物の where

let xs = f
    f = foldl' step s0 [0 :: Int ..]
      where
        s0 = _
        step = _
toyboot4etoyboot4e

ツイート

toyboot4etoyboot4e

スニペットという手があったか

たとえば for3 を 3 重ループに展開するとか。なるほどね! あるいは Copilot を巨大なスニペットとして扱う人もいそうです。

僕はクソデカテンプレートを持ち歩いていますし、 Haskell だとスニペットが不要なほど簡潔なコードを書けることが多いです。でも Tempel を使ってあげたいなぁ

toyboot4etoyboot4e

Misc

  • 競プロのコミュニティがあるらしいけれど
    どこなんだ……毎週 7,000 人も参加しているのに…………

  • けんちょん本 の高評価をよく見る
    初期にこの本を買えばよかったかも。。

toyboot4etoyboot4e

今欲しい Haskell の本

競プロから摂取できる Haskell には限界があります。 Haskell の機能にフォーカスした本が欲しいです。

  • 小〜中規模プロジェクトの開発 (モジュール分割やテストなど)
  • 型族などの言語機能の解説
  • lens などのライブラリの使い方の解説
  • 使えるモナド

たぶん Haskell in Depth が合う気が。

今いらない Haskell の本

関数型プログラミングにフォーカスした Haskell の本。 Richard Bird 先生、難しすぎる……!

toyboot4etoyboot4e

高密度なコードを読み書きするのが楽しくなってきた

要因は慣れです。基本的な関数のシグネチャを覚えてきました。探しものをすると短期記憶が失われますが、そのまま読めるなら便利なことこの上なし!

逆に今の道具立てを失うと、スッキリとコードを書けなくなるかもしれません。関数型言語の中で完結しようとする人たちの目線も少しずつ取り込みたいものです。

toyboot4etoyboot4e

Map vs Sorted array (バランス木 vs ソート済み配列)

メモリ

  • Map: sparse
  • Array: dense

操作

  • Map
    • 更新: K -> V
    • 探索: K → V
  • Sorted array
    • 更新: K -> V
    • 探索: V → K
toyboot4etoyboot4e

ビームサーチは貪欲砲

鉄則本のビームサーチをざっと読みました (A49) 。幅 k のビームサーチは、最善の探索を k 個保持します。入力を受け取る毎にそれぞれのビームから探索を進め、上位 k 本を抽出します。

どのビームを最善手とするかは評価関数が決めます。評価関数の取り方によって、最終スコアが大きく変わります。

toyboot4etoyboot4e

制御構文が未だに分からない

continue や early return のやり方が分かりません。 Maybe モナドはやり過ぎな気がしますし、そもそもモナドを使うのはセンスが悪いような気もします。

Early return 不要にする方作

番兵を使えば分岐を削除できる場合があります。 Haskell だと、手続型プログラミングも技巧的になりがちですね。

toyboot4etoyboot4e

プライドを捨てよう!

  • 解答 AC でもよかろうなのだ!
  • 解答 AC できなくてもよかろうなのだ!
  • TLE を直せなくてもよかろうなのだ!
  • Haskell らしいコードが書けなくてもよかろうなのだ!

2 週目があるさ……

toyboot4etoyboot4e

謎の英単語

intersperse: 間に入れる (a -> [a] -> [a])

Prelude Data.List> intersperse " " ["a", "b", "c"]
["a"," ","b"," ","c"]

intercalate: 間に入れる ([a] -> [[a]] -> [a])

Prelude Data.List> intercalate " " ["a", "b", "c"]
"a b c"

どう違うんだ……

単語のニュアンスは分かりませんが、 Haskell の関数としては理解しました。

toyboot4etoyboot4e

けんちょん本を買ったぞ!

ある程度鉄則本を解いたので、読み物寄りの本を購入しました。これでグラフや DP の考察に強くなれれば……いえ、単純に趣味ですね。

toyboot4etoyboot4e

無限リストを filter しても永遠に制御は帰ってこない

takeWhile にしよう

toyboot4etoyboot4e

sieve (エラトステネスの篩) の高速実装ができない

コピペしよう……

そうか

ふるいの網目 (filter) が増える毎に処理が重くなりますね (実質 xs `elems`) 。網目を IntSet で持てば十分高速だったということになります。

toyboot4etoyboot4e

フェルマーの小定理も万能ではなさそう

フェルマーの小定理を使って逆数の素数 modを計算できる!

a ^ p \equiv a (\mathrm{mod}\ p) \\ a^{p-2} \equiv a^{-1} (\mathrm{mod}\ p)

ところで 6 / 2 \equiv 1 (\mathrm{mod}\ 2) ですが、 6 \cdot 2^{2 - 2} \equiv 0 (\mathrm{mod}\ 2) です。割る数が法の倍数ならば、逆元の計算に使えないみたい。。 (証明を飛ばしているため詳細不明)

まあ階乗の mod 計算には安全に使えます (n < p なら n! は p の倍数ではない) 。より実用的な文脈を知るには、暗号などを勉強すると良いのかもしれません。

toyboot4etoyboot4e

今貴様が参加したのは ABC ではない

ARC だ……! (曜日が逆なので気をつけましょう)

toyboot4etoyboot4e

お気に入り機能の使い方が分かったぞ!

これで Haskell ユーザの動向をウォッチできるわけですね

toyboot4etoyboot4e

鉄則本が A ~ EX までカバーしている

後は単なる実力不足ってコト……?!

toyboot4etoyboot4e

集計を待たなくてもレーティングは分かるぞ!

ブラウザ拡張 (predictor) で競技中のレーティングが見れますし、コンテスト終了時も公式発表を待たずに結果が分かります。おやすみ (・ω・)

Predictor 上のレート表示が集計結果と一致しない

  • 不正ユーザの BAN
  • Predictor 自体のバグ (最近発生)
toyboot4etoyboot4e

いろいろツールがあるみたい

  • AtCoderTags
    投票によって問題を分類分けしてくれます。サイトの見た目からして非常に良さそうです。

  • AtCoder Problems
    ABC の問題一覧 (diff 表示 + AC 状況) や統計など。助かっています。

  • AC heatmap
    GitHub の芝のように AC 数を表示してくれます。

  • AtCoder Type Checker
    同レートの人たちと比較して、回答速度で performance を稼いできたか、それとも AC 数で performance を稼いできたのかを確認できます。僕は残念ながら早解き型でした。

toyboot4etoyboot4e

グラフの連結成分や塗り分け(?) のテンプレートが欲しい

まず既存の API を把握したいです。

Data.Graph だと……! (containers)

内部的には Array Int [Int] です。

  • buildG でお手軽作成 (ただの accumArray)
  • components で連結部分をまとめ上げ (これが便利……!)
  • scc (+ 長さ 1 を削除) で閉路も手に入る (便利……!)

Node, Tree, Forest あたりが複雑なので、直接使用するのは避けようと思います。

UnionFind

  • ルート一覧 (連結成分の個数)
    連結後、 root v == v となる頂点の数を数える
toyboot4etoyboot4e

さらば Data.Graph

Tree が使いづらいので Data.Graph の関数に頼るのは止めました。 topSort だけ借ります。

toyboot4etoyboot4e

けんちょん本でグラフ問題をエアプ

動的計画法なんかはサッと流しつつ、グラフを本格的にやってくれますね。

https://bookclub.kodansha.co.jp/product?item=0000275430

単一始点最短経路問題

Dijkstra 法

エアプじゃないので省略します。昔の僕はヒープが無い環境でプログラミングをしていたので、 O(N^2) の方を実装していました。ヒープって良いですね。

ベルマン・フォード法

(N - 1) 回の状態発展 (そんな用語があるかは知りません) を実施! 負のループ (サイクル) が存在しなければ (N - 1) 回までの発展で必ず収束します。 N 回目の発展で状態変化が起きたかどうかで負のループがあるかを判定できます。

全点対間最短距離 (Floyd–Warshall 法)

DP で解いちゃう!

最小全域木問題 (Kruskal 法)

辺に対する貪欲法! 連結の状態は Union-Find で持てます。

ネットワークフロー

最小カット問題 (辺連結強度の問題)

  • s-t カットの最小容量は辺素な s-t パスの最大本数と等しい
  • 辺素な s-t パスの最大本数は、残余 グラフ ネットワークを考えて取得できる

最大流 (フロー) 問題

  • 辺に重みがある場合の最小カット問題 (最大流最小カット問題)
  • 同様に残余 グラフ ネットワークを考えて計算できる (Ford-Folkerson 法)

なんとなく電流関連の定理 (テブナン?) と似ている気がします。一旦イメージだけ暗記してしまうか

2 部マッチング

始点と終点を追加して最大流問題に帰結!

点連結度

各頂点を 2 分割すれば辺連結強度に関する問題 (よく分からぬ)

プロジェクト選択問題

始点と終点を追加して、制約を重み無限の辺で表現する

積んでいる本

これを読んでおけば良かったなと今思います。 Robert Nystrom もグラフ理論はめちゃめちゃ使ったと言っていましたし。次はこの本か Haskell in Depth か Richard Bird か、それとも章末問題を解いてエアプ卒業か……

https://www.morikita.co.jp/books/mid/085281

toyboot4etoyboot4e

高度なグラフアルゴリズムについて

もう全て忘却しました……。やっぱりエアプじゃダメだ。鉄則本を解いて身につけていきます。

toyboot4etoyboot4e

tempel でスニペット挿入 (Emacs)

たとえば tr C-j!_ = traceShow () () を挿入できます。通常の補完と名前空間が分かれているのが良いですね:

haskell-mode
(traceShow "!_ = traceShow (" (p "") ") ()")

今後は典型的なコードはテンプレートファイルにメモしていこうと思います。

その他便利そうなもの

(yn "if " (p "result") " then \"Yes\" else \"No\"")

(graphGen "input <- concatMap (\\[a, b] -> [(a, b), (b, a)]) <$> replicateM nEdges getLineIntList" n>
          "let graph = accumArray @Array (flip (:)) [] (1, nVerts) input" n>)

(fold "let result = foldl' step s0 " (p "input") n>
      "    s0 = " (p "_") n>
      "    step = " (p "_"))

(for2 "forM_ [0 .. h - 1] $ \\y -> do" n>
        "forM_ [0 .. w - 1] $ \\x -> do" n>)

(runSTUArray "let dp = runSTArray $ do" n>
        "arr <- newArray ((0, 0), (h - 1, w - 1)) (0 :: Int)" n>
        "      return arr")

(VU.create "let dp = VU.create $ do" n>
        "vec <- VUM.replicate (h * w) (0 :: Int)" n>
        "      return vec")
toyboot4etoyboot4e

個別のケースをコードに書きつけても良い

一般解でなくても AC できれば良いのですし、むしろ境界を考慮したコードが書けて良いかもしれません:

main :: IO ()
main = do
  [n, k] <- getLineIntList
  print $ solve n k

modulo :: Int
modulo = 10 ^ 9 + 7

solve :: Int -> Int -> Int
solve 1 1 = 1
solve 1 k = k
solve 2 k = k * (k - 1) `rem` modulo
solve 3 k = k * (k - 1) * (k - 2) `rem` modulo
solve n k = _
toyboot4etoyboot4e

Language Update 2023

vector が更新されると Unbox が簡単に導出できるようになるらしいので、それだけで満足な気がします。

toyboot4etoyboot4e

螺旋本を買ったぞ!

先日のセールで『プログラミングコンテスト攻略のためのアルゴリズムとデータ構造』が Kindle 版 500 円になっていました。もちろん購入したので、 Kindle Scribe (*1) で読んでエアプしていきますよ〜〜

この本の評判としては、意外と競プロ (プロコン) に特化していないとのことです。たとえばソートの内部実装もやってくれます。娯楽としてはちょうど良いと思います。

(*1): マウントポイント

https://book.mynavi.jp/ec/products/detail/id=35408

toyboot4etoyboot4e

小ネタ集

  • IM.fromListWith (+) で sparse な accumArray ができる
    • fromList だと重複した要素が消えるため要注意
    • elemskeys は ascending order (昇順) がデフォルト
    • IM.toAscList, IM.toDescList は共に O(N)
toyboot4etoyboot4e

Lang Update 2023 に備えて

追加予定のパッケージを見ていきます。

Misc

ありがとう有数の識者たち…… 🙏 普段の ABC 参加者は 10 人くらいですし、最新のハイパフォーマンス Haskell に詳しい人はさらに限られます 🙏 🙏ましてアップデートに注力してリサーチしてくれる人は 🙏🙏 🙏

ABC では通れば良いので、高速化ライブラリはヒューリスティックとかハイレート勢向け向けの気がします。しかし提出結果を比較して最適化の効果を実感できるので、趣味のためにもこうしたライブラリはガンガン入れてほしいです。

パッケージ

Hackage と Stackage の違いが分からないことに愕然としました。

  • attoparsec
    速い parsec?
  • bitvec
    めちゃくちゃ速い Bool の vector!
  • deepseq
    seq や bang pattern と違って、データコンストラクタなどの内側まで評価してくれるようです。真に strict な評価が来た?!
  • exceptions
    実行時エラー表示をマシにできるかも? https://qiita.com/aiya000/items/6700753df2dfb9ae942e
  • extra
    準 Prelude 気分。 Data.Tuple.Extra が特に良さそう です。 both とは Bifunctor には無いやつやし、トリプレットへのアクセサはそもそも無いんよ
  • fgl
    グラフのつえーライブラリらしい 。なぜ見逃していた……?
  • integer-logarithms
    Int# が出てくる謎ライブラリ。スルーかなぁ
  • lens
    構造体の部分的更新に使えるらしい? 僕が AtCoder で使うことは無さそうですが、 AtCoder 外の準 Prelude みたいなライブラリを見ることができるのは良いですね。
  • linear-base
    線形型、ということは所有権を Haskell に持ってこられるのだろうか……?
  • massive
    repa 代打?!
  • mono-traversable
    fmap ならぬ omap, traverse ならぬ ofold? Monomorphic container というのは Vec<T> のような普通のコンテナと思いますが、そんな一般的に実装できるのだろうか…… 。要チェックですね。
  • mutable-containers
    Mutable な Deque なんかがあるっぽい?! 鉄則本での純粋な解答を見直す機会になります。
  • mwc-random
    乱数生成 ヒューリスティック向け? topcoder みたいに撃墜システムもありませんしね
  • parallel
    ヒューリスティック向け?
  • parsec
  • psqueues
    Pure priority search queues. よく分かりませんが heaps の代わりになるかもしれません。
  • random
    乱数生成 ヒューリスティック向け?
  • reflection
    なにがなんだか。。
  • regex-tfda
    正規表現
  • safe-exceptions
    推奨されている方のやつ!
  • template-haskell
    噂のやり過ぎてしまうテンプレートシステムだ!
  • tf-random
    乱数生成
  • utility-ht
    ビットシフトの演算子とか、 Enum に対する Ix とか if' とか。 それほしいやつやん!

repa は 1 回も使うことなく消えたよ……

インフラ強化

  • safe-exceptions でマナーの良いコードが書けそう

前からあるじゃん

  • fgl, massive, mutable-containers, psqueues が要チェックです!
  • mono-traversable も見てみたい。
  • extrautility-ht も良し!

最新 Prelude はまたの機会に

こちらも良いものが増えてそう!

toyboot4etoyboot4e

見直すと、大半のライブラリが 2020 年環境で利用できたので履修していきます。逆に何が増えたんだろう…… (safe-exceptions くらい……?)

toyboot4etoyboot4e

本日の ABC は D~ 異様に難しいらしい

つまり分が悪い。早解きと ARC A を解いてみるしかないか〜〜

待って

ARC 序盤って水色頻発地帯ですね……。ヤマ張って EDCP を進めますか……?

新たなリソースが

これはエアプと復習にめちゃめちゃ良いぞ……!

https://mobile.twitter.com/recuraki/status/1611647410249535488

新しい本を買い増した

Richard Bird 先生が難し過ぎた。。鉄則本購入者のセンサーが働く限り、この本がとても良さそう!

https://www.amazon.co.jp/gp/product/B09X9VGG2Y/ref=ppx_yo_dt_b_d_asin_title_o00?ie=UTF8&psc=1

toyboot4etoyboot4e

ABC 負けたよ (・ω・)

開始 60 分で 7,000 位になっていました。 危なかった ……。精彩を欠いた状態は露骨に結果に現れますね。精進しようにも全然解けないし、しばらく守りの番かもしれません。

Immutable な Vector の更新関数があった

これ良いですね。全要素更新するなら generate を使いますが、部分更新をシンプルにできそうです。 foldM を止められる 気がします:

入力がリスト 入力が Vector 更新方法
VU.// VU.update 上書き
UV.accum VU.accumulate 関数適用

ただどちらも O(n + m)なのが気になりました。オーダを下げるには in-place で破壊的な更新がしたい です。 modify は高速 (in-place) になることもらしいので (所有権を追えるのかな) 、ソート以外にも使ってあげたいです:

結局 generatemodify がスタメンになる気がします。

ライブラリを見直そう

実は VUM.nextPermutation のような便利関数もあり、 extra, utility-ht を始め、既知のライブラリを見直した方が良さそうです。

toyboot4etoyboot4e

Dense で immutable な Union-Find を作ってみたい

Union-Find テンプレートの改良を図ります。

  • VU.modify で immutable かつ in-place なデータ更新ができるはず!
  • また vector-th-unbox で自作データ型を Unbox にしたいです。

Union-Find の immutable 実装

Mutable な関数をタプルを返す関数に置き換えます。 State モナドにしてもいいかもしれません

Unbox の自動導出

古い vector パッケージではUnbox の実装するのは至難の業です。解決方法としては、

1. unboxing-vector (sum type 不可)

Vector.UnboxedVector.Unboxed.Mutableunboxing-vector パッケージのモジュールに置き換えます。 Unbox の代わりに Unboxable を使うことになるので、既存のコードも編集が必要です。

c.f. unboxing-vectorの紹介:newtypeフレンドリーなunboxed vector

ただし sum type に対しては Unboxable を実装できない ようです。今回は利用できません。

2. vector-th-unbox (sum type も可)

Template Haskell で Unbox 実装をコード生成します:

-- vector-th-unbox: https://www.stackage.org/lts-16.11/package/vector-th-unbox-0.2.1.7
import Data.Vector.Unboxed.Deriving (derivingUnbox)

newtype UnionFind = UnionFind (VU.Vector UFNode)

-- | `Child parent | Root size`. Not `Unbox` :(
data UFNode = UFChild {-# UNPACK #-} !Int | UFRoot {-# UNPACK #-} !Int

_ufrepr1 :: UFNode -> (Bool, Int)
_ufrepr1 (UFChild x) = (True, x)
_ufrepr1 (UFRoot x) = (False, x)

_ufrepr2 :: (Bool, Int) -> UFNode
_ufrepr2 (True, x) = UFChild x
_ufrepr2 (False, x) = UFRoot x

derivingUnbox "UFNode" [t|UFNode -> (Bool, Int)|] [|_ufrepr1|] [|_ufrepr2|]

Sum type に対しても問題なく実装できています。

謎のパースエラーが出たので関数を分けています。

結果

UV.modify は遅い。全然 in-place に動いてない気がしますね:

種類 boxed Unbox
Mutable (PrimState + INLINE) 463 ms 187 ms
Immutable (VU.modify) TLE TLE

また既設の Union-Find は Unbox 実装で倍以上高速になりました。今回は明らかなミスの修正でしたが、競プロにも盆栽の余地がありますね。だから黒魔術に走る人がいるわけですか (あの方 C++ よりも速いんだもん……) 。

toyboot4etoyboot4e

レートが横ばいだ

今日 80 分かかった 4 問を 20 分で解けるようになるとは思えません。やはり 1,000 問解いて E まで解けないとダメか……!

toyboot4etoyboot4e

急速にレートを上げるには、 F や水 diff が解けないと無理よ……!

toyboot4etoyboot4e

人のコードも読んでみようかな

Haskeller のコードは参考にならないことが多いです。

  • リストを使っている。技巧的で難しいです。
  • 普通の Haskell. 普通に難しいです (絶望)
  • 黒魔術である。 C++よりも速いし当然読めないのである。

ただ今より良いコードを書こうと思えば、他人から吸収するしかないので、そろそろコードリーディングの下地を作って行こうと思います。思うだけかも……。

toyboot4etoyboot4e

Haskell の話

こういう話はあと 10 本ぐらい聞きたい……!

https://podcasts.apple.com/jp/podcast/misreading-chat/id1364330509?i=1000496280601

こっちも……!

https://rebuild.fm/352/

こういうやつも……!

https://podcasts.apple.com/jp/podcast/episode-13-john-wiegley-on-categories-and-compilers/id694047404?i=1000385334618

そういえば動画派もいる

ライブコーディングという手もあったか……! しっかりついていく必要があるので、あまり観ることはないのですが、勉強にはなるので時間を割くべくかもですがうーむ

https://m.youtube.com/@sampouorg

https://zenn.dev/magurotuna/articles/d5621291b8da87

toyboot4etoyboot4e

バーチャルコンテストだと……!

とりあえずこれに出る……!

https://mojacoder.app/users/Ajinoko/contests/Ajinoko_ASC ジャッジが Haskell 非対応だ! 参加登録してごめんなさい!

定期開催

競プロのコミュニティはあるのか無いのかよく分かりません。

結局は

一人でやろうということに。

toyboot4etoyboot4e

PAST 上級本を買ったぞ!

3 月末発売ということで楽しみにしています。

https://www.amazon.co.jp/アルゴリズム実技検定-公式テキスト[上級]~[エキスパート]編-Compass-Booksシリーズ-大槻兼資-ebook/dp/B0BV9WVTF8?crid=25GSS1S3FF0UA&keywords=アルゴリズム実技&qid=1676200145&s=books&sprefix=あるごりずむじつ,stripbooks,195&sr=1-2&linkCode=shr&tag=rust-twitter-22&language=ja_JP&ref_=as_li_ss_shr&creativeASIN=B0BV9WVTF8&camp=1207&creative=undefined&linkId=6e7b1dd718bd60586e99625dae13d675

Thunder 本も買ったぞ!

今週出ますね。かなり楽しみです。

https://www.amazon.co.jp/dp/4297133601/

ヒューリスティックは言語の実行速度が点数に直結するはず (?) なので、 Haskell は辛いんじゃないかと思います。ここはまさかの Common Lisp か……? 何か面白い言語を探したいですね

toyboot4etoyboot4e

毎日 3 問解き続けた 3 か月目の自分に負けるんだろうな

今 AtCoder 9 か月目なのですが、 AC 数 227 を見てそんな計算ができました。身近な夢から叶えていこう

toyboot4etoyboot4e

NixOS 環境構築 脱獄失敗編

あるいは nixpkgsghc883 が無い問題。。 ghc884 を使って回避を試みました。

いや

Template Haskell を使っているせいか、 ld にリンクできないというエラーが出ました。いろいろ辛いね……

https://github.com/haskell/haskell-language-server/issues/1841

HLS のバージョンを 1.5.1 から 1.8.0 に上げてみたいと思います。 ソース をGHC 8.8.4 をパスに入れた上で nix-shell -p zlib から ビルド して……。

できた

変数のリネームもできますし、こっちのほうがいいかも……!

toyboot4etoyboot4e

ABC 290 (日曜開催) も半 ARC 仕様だ

すなわち早解き会……!

toyboot4etoyboot4e

辞書順

ABC 276 C - Previous Permutation など

辞書順で何番目?

Chokudai Speed Run 001 - K

O(N^2) の解答しか思いつかないので保留……。セグ木でいっかぁ

辞書順で次の順列は?

具体例を見ると:

  • 123 45 → 123 54
  • 1 2543 → 1 3245

つまり後ろから見て単調増加が崩れた点以下を加工します。

辞書順で 1 つ前の順列は?

具体例を見ると:

  • 543 21 → 543 *12
  • 54 312 → 54 231

つまり後ろから見て単調減少が崩れた点以下を加工します。

加工のやり方

めちゃくちゃ頑張る。。

nextPermutationprevPermutation を作る

Haskell にも Vector.Mutable.nextPermutation があります。逆向きに nextPermutation するには、 Ord の向きを替えてしまえば良いので、それぞれの桁を Down で包みます:

prevPermutationVec :: (Ord e, VG.Vector v e, VG.Vector v (Down e)) => v e -> v e
prevPermutationVec =
  VG.map (\case Down x -> x)
    . VG.modify
      ( \vec -> do
          _ <- VGM.nextPermutation vec
          return ()
      )
    . VG.map Down

なお const () <$> VUM.nextPermutationVUM.nextPermutation >>= \_ -> return () とは書けません。 ST モナドのライフタイム制限は厳密……! (この例は安全側なので通してほしいけれど)

toyboot4etoyboot4e

GHC 8.8.4 + lts-16.31 + HLS 1.8.0.0 がいいかもしれない

GHC 8.8.3, lts-16.11 と大差無い環境で、しかし変数のリネームができる!

なにより nixpkgsghc884 があるので NixOS でも環境構築できるのがありがたいです。難しすぎだよこの OS ……

toyboot4etoyboot4e

groupBy が思っていたのと違った

Data.ListgroupBy

Prelude Data.List> groupBy (\a b -> a + 1 == b) [1, 2, 3, 4]
[[1,2],[3,4]]

リストの先頭の要素と次の入力を比較しています。リストの末尾の要素と比較してくれませんか!!、

https://stackoverflow.com/questions/1316365/haskell-surprising-behavior-of-groupby

== と同じ性質を前提とするみたいです。詳しく読んでいません。。

utility-ht

こちらの groupBy は隣接要素を比較するとか。完全にこれだと思っていました……

> Data.List.HT.groupBy (\a b -> a + 1 == b) [1, 2, 3, 4]
[[1,2,3,4]]

HT.groupBy という形で使っていこうかな。あるいは groupBy を使わないのが無難かもしれません。

toyboot4etoyboot4e

そろそろ人のコードを読もう

ABC 290 D の提出 を読みます。

モチベーション

実行時間 41 ms ですって。 C++のどの提出よりも速いのです。バケモンだよ……!


冗談だろ、ハニー
Haskell は AtCoder で 2 番めに速い言語
我々がロブスターならば、彼はレックウザであると言わざるを得ない

冒頭

  • まずは LANGUAGE pragma から。
    • フォーマッタが ormulu ではないようです。
    • CPP を定義して #define が有効になっています。
    • GHC.Exts に primitive 型があります。 Haskell では Int も boxing の対象になるらしいですからね……
  • #include MachDeps.h
    • 今、何が起きた……?

マクロ

1 行で INLINE プラグマ。まぁ #[inline] に比べて {-# INLINE <name> #-} はセンス悪いですよね:

#define IL(f) {-# INLINE f #-}; f

入出力

ABC 290 D では以下の入力が与えられます:

3
4 2 1
4 2 2
4 2 3

パース部分。 rIntL, rIntL, rInt1LInt のパーサで、それらの出力を liftA3 (,,) を使ってタプルにまとめています:

main = do
  t <- readLn
  qry <- getVecURest t $ liftA3 (,,) rIntL rIntL rInt1L

rIntL, rInt1L

rIntLInt を読み、 rInt1L は 1-based index な入力を 0-based な入力に変換します 。 Haskell では shadowing 非推奨なので超重要! これなら NPlusKPatterns に依存せずにシュッと書けます。上手い:

IL(rIntL) :: StateT BSL.ByteString Maybe Int
rIntL = skipSpecialL $ StateT BSL.readInt

IL(rInt1L) :: StateT BSL.ByteString Maybe Int
rInt1L = subtract 1 <$> rIntL

TODO: なぜ StateT?

rIntLL とは left かな? 実装を追います:

IL(skipSpecialL) :: (MonadState BSL.ByteString m) => m a -> m a
skipSpecialL = (dropSpecialL *>)

IL(dropSpecialL) :: (MonadState BSL.ByteString m) => m ()
dropSpecialL = modify $ BSLW.dropWhile (< ord8 '!')

-- import qualified Data.ByteString.Lazy as BSLW
  • MonadStateget とか put のような State の基本的な API っぽいですね。ここ、すごい H 本で出てきた所だ!

TODO: 頑張って読む。

getVecURest

getVecURest は generic な実装に委譲しています:

-- import qualified Data.ByteString.Lazy.Char8 as BSL

IL(getVecURest) :: (VU.Unbox a) => Int -> StateT BSL.ByteString Maybe a -> IO (VU.Vector a)
getVecURest = getVecGRest

Generic な方の実装:

IL(getVecGRest) :: (VG.Vector v a) =>
  Int -> StateT BSL.ByteString Maybe a -> IO (v a)
getVecGRest n s = VG.unfoldrN n (runStateT s) <$> BSL.getContents

WIP

toyboot4etoyboot4e

記事を書きました

Haskell for AtCoder な記事はこちらです。タイトルに比べて極めて個人的な内容なのがまずい気も……

他に書きたいのは黒魔術編、ガバベンチ編、コード編です。また AtCoder に限らず、モナドやエフェクトにも興味があるので追々調べます。

toyboot4etoyboot4e

メモめも

GHC 8.8.3 は Windows でバグっているらしい

https://teratail.com/questions/338224

ほな 8.8.4 でええか!

QuickCheck でテストケース生成 :eyes:

https://blog.miz-ar.info/2020/08/debugging-with-quickcheck/

  • 制約を課してテストケース生成
  • ナイーブな実装と効率的な実装の出力を比較
  • 不一致のケースを割り出し
  • debug

doctest でライブラリ整備

https://haskell.e-bigmoon.com/stack/test/doctest.html

Rust の cargo test --doc みたいなやつですね。

toyboot4etoyboot4e

小物

仮案

tuple2, tuple3 がかなり便利です。 uncurryした通常の関数に入力を丸ごと渡せることがあります:

getLineIntList :: IO [Int]
getLineIntList = unfoldr (BS.readInt . BS.dropWhile isSpace) <$> BS.getLine

tuple2 :: [Int] -> (Int, Int)
tuple2 [!a, !b] = (a, b)
tuple2 _ = error "not a two-item list"

tuple3 :: [Int] -> (Int, Int, Int)
tuple3 [!a, !b, !c] = (a, b, c)
tuple3 _ = error "not a three-item list"

getTuple2 :: IO (Int, Int)
getTuple2 = tuple2 <$> getLineIntList

getTuple3 :: IO (Int, Int, Int)
getTuple3 = tuple3 <$> getLineIntList

catPairs :: [(a, a)] -> [a]
catPairs [] = []
catPairs ((!x, !y) : xys) = x : y : catPairs xys

concatMap2 :: (a -> (b, b)) -> [a] -> [b]
concatMap2 f = catPairs . map f

また concatMap2 と extra パッケージがあれば、グラフ生成をポイントフリースタイルで書けます:

getGraph :: Int -> Int -> IO (Array Int [Int])
getGraph nVerts nEdges = accGraph . toInput <$> replicateM nEdges getLineIntList
  where
    accGraph = accumArray @Array (flip (:)) [] (1, nVerts)
    toInput = concatMap2 $ second swap . dupe . tuple2

重み付きグラフを格好良く書くのははちょっと厳しいかな……

入力処理の汎用性について

関数を分けるより、型クラスを作って getLine' @(Int, Int) みたいに書くほうが良い?

パースの方法 (空行区切り) も明示的に指定したくなってきますね。まぁ Int で空白区切り限定でも良いのではないでしょうか。

toyboot4etoyboot4e

基本的な構文や言語機能に関し

合意できる点も増えたのでノートします。

  • if, then, else
    キーワードが分かれているおかげで、それぞれの句で $ が使えます。

  • Public item を module キーワードで定義する

    • Module キーワード以下は簡易 DB とみなし、エディタのコマンド経由で item を追加・削除すれば良いです。
    • Public item をソース上でハイライト表示すれば、 pub キーワードが無くても公開性を一目で見れます。
  • 言語拡張が無いと弱い
    うーん

  • INLINE, define, template haskell
    うーん

  • モナドを合成すると重くなりがち
    うーん

  • 所有権が無い、遅延評価を使う
    うーん。研究が進めば Haskell, Rust の次の言語が出てくれるのでしょうか。

toyboot4etoyboot4e

黒魔術 Haskell からの植え替え・枝抜き

ShowBSB (ShowAsBuilder)

ByteString は普段入力にしか使っていないのですが、出力にも使いやすいように easy なコードを追加します。

まず ByteStringprint 類に別名を与えます:

putBSB :: BSB.Builder -> IO ()
putBSB = BSB.hPutBuilder stdout

printBSB :: ShowBSB a => a -> IO ()
printBSB = putBSB . showBSB

そして ByteString.Builder への変換を共通のインターフェースで統一します

{-# LANGUAGE DefaultSignatures #-}

-- | Show as a bytestring builder
class ShowBSB a where
  showBSB :: a -> BSB.Builder
  default showBSB :: (Show a) => a -> BSB.Builder
  showBSB = BSB.string8 . show

instance ShowBSB Int where
  showBSB = BSB.intDec

instance ShowBSB Integer where
  showBSB = BSB.integerDec

instance ShowBSB Float where
  showBSB = BSB.floatDec

instance ShowBSB Double where
  showBSB = BSB.doubleDec

空行区切りで Vector を print する

WIP. 逐次 print vs ByteStringBuilder 一括出力?

WIP

toyboot4etoyboot4e

永年初心者

$ 演算子の謎

コンパイルできない:

countCost v = costs VU.! v + sum $ map (costs VU.!) $ filter (`IS.notMember` visited) $ graph ! v

コンパイルできる:

countCost v = (costs VU.! v +) . sum $ map (costs VU.!) $ filter (`IS.notMember` visited) $ graph ! v

関数適用の優先度が最も高いならコンパイルできそうなものですが、 $ が絡むと謎が多い……

toyboot4etoyboot4e

$ の左辺は関数ということでしょうか。未だ基礎的なメンタルモデルに漏れがあります。

unfoldr の step 関数が $ の時もあって謎です。 Haskell のサブセット、自分方言しか扱えません。自分の方言を強くしていこう!

今は vector の stream を使えば foldUntilVG が作れるんじゃないかと食指を伸ばしつつあります。

toyboot4etoyboot4e

ループ処理

  • (!! n) $ iterate step s0: n 回繰り返す
  • foldl' step s0 input: 畳み込み / 入力処理
  • until pred step s0: ループ / early return

foldl'until の合わせ技が欲しい気もします。

追記

(!! n) $ iterate step s0非常に低速で TLE になった ので止めましょう。

toyboot4etoyboot4e

そうか

再帰関数による入力処理が、 folduntil の合わせ技に相当しますね。再帰関数は書かない派でしたが、転向すべきかもしれません……。折衷案を探したいです。

toyboot4etoyboot4e

loop, loopM (extra)

これは until の亜種ですが predicate とループ関数が一体化しています。

入力を Vector に入れておけば、 untilloop は入力を添字アクセスで取り出すことで fold も兼ねることもできますね。それをするよりは foldUntil のような関数を作りたいですが……

toyboot4etoyboot4e

テーブルのサイズと DP の難度

2 要素のみ

fold で解けます。 DP という意識が湧きません。

N 要素のみ (ナップサック問題など)

fold で解けます。到達可能な範囲のみ更新するように気をつけます。そこそこ簡単な印象です。

N * N 要素 (最小共通部分列、区間 DP など)

途端に難しい。

N 要素 + N 要素 (最長増加部分列など)

相当難しい。

toyboot4etoyboot4e

F 問題まで解けないと入水は厳しい

恐ろしい世界だ……年単位で時間をかけられるなら話は別ですが……

toyboot4etoyboot4e

日報

Name shadowing は許可してしまうか

let visited' = IS.insert v visited とやった後に visited を使うことはありません。むしろ name shadowing を許可したほうがスコープが整理されて安全でしょう。やってしまいます:

stack.yaml
  ghc-options:
    -Wno-name-shadowing

ただ思わぬ再帰で無限ループを作らないように気をつけます。

追記: let visited =IS.insert v visited の時点で無限ループになる……? (悲しみ)

TODO: Stack script に警告抑制に相当するコメントを書き込む?

手続き型 Haskell が辛すぎる

1 問 6 時間かけて 謎に AC できない かろうじて AC するし、無残なコードがどうにもならない。そんなペースです。今、緑問題 streak 週間だから眠れないんだ……

Vec が無いのが辛すぎる

最大流問題の実装で苦しんでいます。 Vector は基本固定長配列なのです。 Capacity と lenght の区別も無いし……

頂点数を事前に数えて MVector を使うか、 IntSetSequence で誤魔化すか……。まずは [Int] IntSet で実装してから、後に盆栽します。剪定します? それともパフォーマンスは追求しなくていいか……

foldM に early return が無いのが辛い

定石がほしい。。ガードで止めてますが、空回りが残るのは気になります、

forM に early return が無いのが辛すぎる

if の多重ネストになっています。 StateT で返値書き込みかなぁ

悩みどころは無限にあります。やはりハイレート Haskeller の盆栽的積み重ねがすごい……

AC した

天才! まあ酷いコードなので、リファクタリングの手段を探したいと思います。いつ見つけられることやら……

それでも上手く行ったときのことを考えてしまう

時間ができた瞬間に都合の良い想像ばかりしてしまいます。何もかも上手くやり、理想的なプログラミングに近づいたとしたらと思うと、まだまだ離れる気にはなりません。 macOS も Git も Vim も Rust も Evil Emacs もそうやって習得してきたので、脳みそが変質するタイプの博打からは中々撤退できません。

マゾではないですが引き続きやっていきます。今のテーマは『Haskell で妥協の無い手続き型プログラミングがしたい』。今後は難問でこそ黒魔術的解法を調べてたいと思います。

toyboot4etoyboot4e

結局

副作用の無いプログラムを分離して (効率を落としつつ) 簡単なプログラムにするのが無難かなぁ。速いプログラムを書くならやっぱり Rust なのか……

toyboot4etoyboot4e

テンプレートが 1,000 行を超えた

大半はコメントです。 AtCoder ではコード行数の下三桁しか表示しないことを確認しました (スマホの場合) 。

#include の検討

クソデカテンプレートはコンパイル時間にも悪そうです。特定の問題以外で使用しないコードは include して使うのが良いかもしれません。エディタ上で Import 類も削減できて良さそう……

  • CPP#include を使った Stack Script は動きます。
  • HLS は CPP#include を理解します。

#include を展開したい

stach ghc -- -E a/Main.hs の結果は gcc -E っぽいです。謎のコメントが含まれていてうーむ。。

これを動かしてみよう

./script extract problem/Template.hs の結果は #include が展開されている! これなら AtCoder に提出できます。

https://github.com/mizunashi-mana/haskell-atcoder-template

gcc -E -P -w -xc -traditional

  • -E: pre process の結果を吐く
  • -xc: 標準出力に書き出し
  • -P: 出力に linemarkers (# ..) を含めない
  • -w: 警告を出さない

-traditional がよく分からないですが、入れておいたほうが良さそうです。

Linux で動かしましたが、リポジトリの作者が macOS ユーザのようなので (pbcopy を使っている) macOS でも動きそうです。

#include のみを展開したい

しかし #include <file-not-on-atcoder> 以外は GHC が理解できるので #include だけ展開したい。ツールを作るか……!

toyboot4etoyboot4e

Deno で #include を展開する

すなわち ^#include な行をファイル内容で置換します。完全に初見なのでやっつけで……。そもそもドキュメントはどこなんだろう

#!/usr/bin/env -S deno run --allow-env --allow-read
// -*- mode: TypeScript; -*-

// Expands `#include` directive (only)
// TODO: Learn TypeScript and write better code

import { readLines } from "https://deno.land/std/io/mod.ts";

let out = "";
let err = "";

for await (let line of readLines(Deno.stdin)) {
  if (line.startsWith('#include')) {
    let file = line.split('"')[1];
    try {
      out += await Deno.readTextFile(file);
    } catch (e) {
      err += "unable to find file: " + file;
      err += "\n";
    }
  } else {
    out += line;
    out += "\n";
  }
}

if (err == "") {
  console.log(out);
} else {
  console.error(err);
}

あまりにも fold したい

というか Haskell でやるか?!

toyboot4etoyboot4e

Haskell で #include を展開する

効率は置いといてできました:

#!/usr/bin/env stack
{- stack script --resolver lts-16.31
--package bytestring --package containers --package extra
-}

module Main (main) where

import Control.Monad
import Data.Char
import Data.Either
import Data.Maybe
import Data.Monoid
import Data.List
import System.IO

{- ORMOLU_DISABLE -}

import qualified Data.ByteString.Builder as BSB
import qualified Data.ByteString.Char8 as BS

import Data.Tuple.Extra

{- ORMOLU_ENABLE -}

-- | Taken from latest `extra`:
(!?) :: [a] -> Int -> Maybe a
xs !? n
  | n < 0     = Nothing
             -- Definition adapted from GHC.List
  | otherwise = foldr (\x r k -> case k of
                                   0 -> Just x
                                   _ -> r (k-1)) (const Nothing) xs n

-- | Expands `#include` line with the file content.
translate :: Int -> BS.ByteString -> IO (Either BS.ByteString BS.ByteString)
translate i line
  | not $ BS.isPrefixOf (BS.pack "#include \"") line =
    return . Right $ line <> (BS.pack "\n")
  | otherwise =
    case (!? 1) $ BS.split '"' line of
      Nothing ->
        let err = "Unresovled import in line" ++ show i ++ ": "
         in return . Left $ (BS.pack err) <> line
      -- TODO: Handle exception
      Just file -> Right <$> BS.readFile (BS.unpack file)

main :: IO ()
main = do
  lines <- zip [0..] . BS.lines <$> BS.getContents
  (errs, lines) <- partitionEithers <$> forM lines (uncurry translate)

  BS.hPut stdout $ if null errs
    then mconcat lines
    else mconcat errs
toyboot4etoyboot4e

#include すると HLS の調子が悪い

この辺の警告を消せませんでした:

  • The import of 'Data.IORef' is redundant
  • Defiend but not used

Package.yaml の ghc-options-Wno* を追加したり <project>.cabal も更新したのですけれどね。むむむ

toyboot4etoyboot4e

時間がある

すなわちエアプの時間が……!

2-3 finger tree

Haskell に特化した記事が Wikipedia にありました。 2 分木、 3 分木の亜種であり、ルートの位置を変えたり複数のルート持つことで、特定種別のアクセスに強くなる。内部実装は追えていないためエアプです。

https://ja.wikipedia.org/wiki/2-3_フィンガーツリー

3 分探索

2 分探索は単調増加する関数に対して有効です。 3 分探索は 2 次関数の極値などを見つけるために使えるようです。

問題によっては与えられた関数を紙の上で微分するだけで済みます

https://qiita.com/ganyariya/items/1553ff2bf8d6d7789127

なるほど。区間 [x_1, x_2] の中に極値があるとして、次の [x_1', x_2'] を作って 3 区間に分割すると、 4 点の大小を比較してどの区間内に極値があるのか分かると。

\begin{bmatrix} x_1' \\ x_2' \end{bmatrix} = \begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \end{bmatrix} / 3

エアプだしこれだけでいっか……! (完)

エアプのコツは

  • 分かった気にならないこと (これは簡単のつもり)
  • 分かった気になっていると思われないこと (これは難しい)
  • 分かっていると思われないこと (これはこれで難しい)
toyboot4etoyboot4e

massive でタプルを格納できる UArray ができそう

内部的には vectorprimitive を使っている風ですね。これなら タプルもしまえるしスライスもできる ! 盆栽項目が増えてきた……!

TODO

生成・インデックス

  • どの型クラス? Manifest?

MArray の代わり

  • write, read, modify, swap,
  • new: ゼロ値初期化
  • createArray?

runSTUarray の代わり

Freeze / thaw

toyboot4etoyboot4e

コンテナを受け取る時は、必ず Bang Pattern を使おう?

Bang Pattern が効く瞬間を初めて見ました。これでスピードが倍になります。 Unbox ばりの高速化ですね:

-  let results = VU.create $ do
+  let !results = VU.create $ do
        !fw <- newFW_ABC286E (\v -> (1, values VU.! v)) nVerts edges
        runFW_ABC286E nVerts fw
        return fw

{-# INLINE #-} は効くのだろうか

PrimMonad を使う関数は {-# INLINE #-} するべきのはずですが、今回は影響ありませんでした。

※ 高階関数も使っているので、複雑な状況ではあります

toyboot4etoyboot4e

Bang pattern で TLE / AC が分かれる他の例

AbC 273 D で出会いました。

  • TLE: 3319 ms
  • AC: 1155 ms

こちらが問題のコードです:

-  let (rmap, cmap) = VU.foldl' step s0 wallInput
+  let (!rmap, !cmap) = VU.foldl' step s0 wallInput
      s0 = (IM.empty, IM.empty)
      step (rs, cs) (r, c) =
        ( IM.insertWith IS.union r (IS.singleton c) rs,
          IM.insertWith IS.union c (IS.singleton r) cs
        )

少なくとも、 Vector を元にコンテナを作る場合は、絶対に ! を付けたほうが良い ですね。安全のため、 Vector を使う場合は必ず ! を付ける、くらいに考えておきます。

toyboot4etoyboot4e

入力を読み込む部分にも ! を付けたほうが良い

メモ使用量が倍になったりするので、 main 関数内にも ! をつけていきましょう。

StrictData 拡張

一応 ON にしておきますが、上記 TLE は StrictData 拡張だけでは回避できませんでした。

追記: こちらユーザ定義型のフィールドに ! を付けるだけのようです。

Strict 拡張

ほぼ何でも正格に評価してくれるはずですが、 where 句の思わぬ式が評価されて死んだりするのでやめておきます。かなり慣れが必要そう……。

toyboot4etoyboot4e

タプルなのか……!

またも ! を付けまくって TLE 突破! ! 無しだとタプルがサンクになるらしいので、その影響なのでしょう。テンプレートは ! だらけにしておきます。

toyboot4etoyboot4e

何でも ! が効くな……!

今まで ! が効かない例ばかり見つけていたのですが、ここ数時間で ! を付ければつけるほど速くなる例を何度も見ました。如実に影響している……!

toyboot4etoyboot4e

ST モナドの変換子が欲しい?

ST モナドの中にある 2 つの可変配列 (探索済み頂点と計算結果) を扱える文脈を作るには……?

TODO

toyboot4etoyboot4e

Data.Graph の使い方が分からない

Forest, Tree / Node がメシー。。もう topSort 以外使いません。

Buffer のテンプレートがある

爆速 Haskller の方が push_back できる vector のようなデータ型 (Buffer) をお持ちでした。リサイズはできないようですが、一度アロケートしたサイズ内なら追加・削除ができる……!

toyboot4etoyboot4e

do() を減らすことができる

便利なときがあるかも?:

-  input <- concat <$> replicateM 9 (map (== '#') <$> getLine)
+ input <- concat <$> replicateM 9 do map (== '#') <$> getLine

この例だと () を付けたほうが分かりやすいと思いますが、改行して延々と関数を作ることもできます。

toyboot4etoyboot4e

リスト内包表記を List モナドで置き換え

リスト内包表記は builtin 構文 (のはず) なので、なんとなくズルをしている気分になります。一般的な do 記法に置き換えてみます。

コード

リスト内包表記:

  let !pos =
        [ [a, b]
          | a <- range bounds_,
            b <- range bounds_,
            a /= b
        ]

リストモナド:

   let !pos = do
        a <- range bounds_
        b <- range bounds_
        guard $ a /= b
        return [a, b]

guard の正体

Control.Monad#guard を見るに、 MonadPlus 上で定義されていますね:

Conditional failure of Alternative computations. Defined by
guard True  = pure ()
guard False = empty

上の例に戻ると、 do 記法の各行はポイントフリースタイルで書かれた関数であり、コード上に現れないリストを文脈のように受け渡ししています。その値が一度 empty ([]) になると、以降の >>=, >> は何もしなくなり、したがって guard 関数は early return を表現できる。ということだったはず。

動作上も early return にしてほしいですが、モナドに関しては実行モデルよりも表現するところを読み取る方が重要かと思います。某 Effects で実行モデルも妥協無い形になると良いのですが、きっとまだまだ研究中ですよね。

do 記法の desugaring

  let pos =
         range bounds_ >>=
           (\a -> range bounds_ >>=
           (\b -> (if a /= b then pure () else []) >>=
           (\_ -> return [a, b])))

pure () の部分が理解できません。文脈はリストではないのか……? traceShow してみると、 a とか b はリストの中の単一の値を受け取っていますね。謎。。

あー >>= の中で concatMap している らしいです。 >>= をもう一段階 desugaring するべき ですね。モナド難しいよ!

IO モナドの empty

手続き型プログラミングでも early return 風の構文を、と思うと失敗します:

ghci> import Control.Applicative
ghci> empty :: IO ()
*** Exception: user error (mzero)

System.IO いわく:

Applicative IO
Takes the first non-throwing IO action's result. empty throws an exception.

MonadPlus IO
Takes the first non-throwing IO action's result. mzero throws an exception.

Applicative と Monad の関係を復習

Control.Monad を見るに、今の Haskell では MonadApplicative の拡張として定義されています。 すごい H 本の説明は (未来予測していたとはいえ) 古い ということですね。

purereturn は同じ、 <*>>>= の関係は以下:

m1 <*> m2
m1 >>= (\x1 -> m2 >>= (\x2 -> return (x1 x2)))

Applicative スタイルで書くとちょっとプロっぽい。

toyboot4etoyboot4e

今こそすごい H 本を周回しよう!

リストモナド

  • concatMap はパタンマッチの失敗を許さない。
  • do 記法なら filter を兼ねることができる。さながら concatMapFilter か。
toyboot4etoyboot4e

(a, b) -> ((a, b), (b, a))

dupe してしまう

ghci> import Data.Bifunctor
ghci> dupe x = (x, x)
ghci> swap (a, b) = (b, a)
ghci> second swap $ dupe (1, 2)
((1,2),(2,1))

Applicative スタイル

ghci> (,) <$> id <*> swap $ (1, 2)

こちらは汎用性が高く、入力の読み込みに使われている方もいますね:

(,) <$> int <*> int
toyboot4etoyboot4e

自作の Union-Find を凍結可能にする

VU.create に相当する createUF 関数を作りたいと思います。

そういえば

読み出し中に木を書き換えないと高速化が望めないので、凍結はできませんでした。

toyboot4etoyboot4e

昔解けなかった問題が解けるようになっている

実装能力と腕力向上のおかげです。特に既成ツールを使うだけで解ける問題はヌルっと AC できて良い感じです。 束の間ですが、 1 問 6 時間時代を突破して収穫の時間がやってきています。

toyboot4etoyboot4e

当然今も解けない問題がある

6 時間コース、ですら終わらないか……!? まあぼちぼち……

toyboot4etoyboot4e

ラムダ式の引数で bang patterns

\(a) のように書くか、 \ の後にスペースを入れます (こっちが推奨かも):

let f1 = \(!x) -> 2 * x
    f2 = \ !x -> 2 * x
    g1 = \(!x) !y -> x + y
    g2 = \ !x !y -> x - y
toyboot4etoyboot4e

IxIntMap, IxIntSet を作ってしまってもいいかもしれない

グラフの問題で boxing なデータ型を使用すると死にます。うまく次元を減らすべく考察するか、そもそも IntMapIx 越しのインターフェースで使うことで素直に解けるようになるはずです。

IxIntSet

作りかけていましたが、 Data.Ix(i, i) -> Int -> i が無いので foldlIxIS を作れないことに気づいてしまいました。

また単ファイル内にモジュールを作れないため、関数名は *IxIS となっています。ちょっと使い勝手が悪すぎますね。 IxIS.* ならハイライトも良いのですが。。

さらに Ix では (Bool, Int) のようなキーに対応できないことは代わりありません。お蔵入りとします。

IxIntSet (作りかけ)
-- {{{ `IxIntSet`

-- | `IntSet` with `Ix` access
data IxIntSet i = IxIntSet (i, i) IS.IntSet

emptyIxIS :: Ix i => (i, i) -> IxIntSet i
emptyIxIS bounds_ = IxIntSet bounds_ IS.empty

fromListIxIS :: Ix i => (i, i) -> [i] -> IxIntSet i
fromListIxIS bounds_ is = IxIntSet bounds_ $ IS.fromList (map (index bounds_) is)

singletonIxIS :: Ix i => (i, i) -> i -> IxIntSet i
singletonIxIS bounds_ i = IxIntSet bounds_ (IS.singleton $ index bounds_ i)

insertIxIS :: Ix i => i -> IxIntSet i -> IxIntSet i
insertIxIS i (IxIntSet bounds_ is) = IxIntSet bounds_ $ IS.insert (index bounds_ i) is

deleteIxIS :: Ix i => i -> IxIntSet i -> IxIntSet i
deleteIxIS i (IxIntSet bounds_ is) = IxIntSet bounds_ $ IS.delete (index bounds_ i) is

memberIxIS :: Ix i => i -> IxIntSet i -> Bool
memberIxIS i (IxIntSet bounds_ is) = IS.member (index bounds_ i) is

notMemberIxIS :: Ix i => i -> IxIntSet i -> Bool
notMemberIxIS i (IxIntSet bounds_ is) = IS.notMember (index bounds_ i) is

nullIxIS :: Ix i => IxIntSet i -> Bool
nullIxIS (IxIntSet _ is) = IS.null is

sizeIxIS :: Ix i => IxIntSet i -> Int
sizeIxIS (IxIntSet _ is) = IS.size is

unionIxIS :: (Ix i, Ord i) => IxIntSet i -> IxIntSet i -> IxIntSet i
unionIxIS (IxIntSet bounds_ is1) (IxIntSet _ is2) = IxIntSet bounds_ (IS.union is1 is2)

intersectionIxIS :: (Ix i, Ord i) => IxIntSet i -> IxIntSet i -> IxIntSet i
intersectionIxIS (IxIntSet bounds_ is1) (IxIntSet _ is2) = IxIntSet bounds_ (IS.intersection is1 is2)

filterIxIS :: Ix i => (i -> Bool) -> IxIntSet i -> IxIntSet i
filterIxIS p (IxIntSet bounds_ is) = IxIntSet bounds_ (IS.filter p is)

-- foldlIxIS :: Ix i => (i -> a -> i) -> a -> IxIntSet i -> a

-- }}}

やはり UnboxSet, Map が欲しいのだけれど

宛がありません。むむむ。。

toyboot4etoyboot4e

緑 diff streak 週間を高く評価してくれるグラフ

それが Top player-Equivalent Effort (TEE) グラフ……!


うなぎ昇りじゃ

toyboot4etoyboot4e

わしのテンプレートは 1,235 行 あるぞ

ふぉっふぉっふぉ

toyboot4etoyboot4e

日報

練習では AtCoder の表示言語を英語にしてみる

本番では英語の誤字が多いので、練習だけの予定ですが英語にしてみます。

グラフの基礎をエアプ

無向グラフ上で連結成分を求めよ

DFS です。

木の直径を求めよ

全経路 DFS 2 回で得た両端の点の間の距離です。これはやったことがあります。

https://algo-logic.info/tree-diameter/

連結成分を求めよ

DFS か

DAG 上で強連結成分を求めよ

これは面白い話ですが、ヒープが必要そうで難しい気がします。 DFS するだけって本当……??

事前知識:

  • 強連結成分を 1 つの頂点に置き換えると DAG (directed asyclic graph) になります。
  • 強連結成分の取得はトポロジカルソートと密接に関わります。

手順:

  • 全頂点から帰りがけ順 (post order) の DFS を実施してラベリングする
    (上流ほど高い値が与えられる)
  • ラベル値が大きい順に DFS を実施する
    (上流の SCC から順に求まる)

https://hkawabata.github.io/technical-note/note/Algorithm/graph/scc.html
https://www.slideshare.net/hcpc_hokudai/study-20150107

トポロジカルソートせよ

cocnat . scc で求まりそうです?

グラフの基礎・典型が広すぎる

鉄則本をやってけんちょん本を読んだところでまだまだ。地道に蟻本を読んでみようと思います。

花粉のダメージを受けておる?

鈍くなっており分からぬ、、涙はあります

toyboot4etoyboot4e

実装の目処がつきました

  • DAG の topSort は postorder の DFS で求まる (けんちょん本)
  • ループありの topSort は scc しか無さそう?
  • scc に置ける番号の大きい順の走査とは、スタックから順に pop していくだけで済む
    https://kanetai.hatenablog.com/entry/20140209/1391915669

スタックのおかげで簡単になりました。逆グラフは O(Mn) で重そうですが……。

このスタックの使い方を見るに、 scc の返値は連結成分毎ではないのかもしれません。それぞれの連結グラフの上流の成分から順番に切り出して渡してくれるイテレータのように見える時もあるかも。連結グラフが 1 つだけなら上流から順番に出してくれますが。

なお scc にはループの場合と双方向に繋ぎ合っている場合があり、 Data.Graph には区別する API もあるようなので参考にします。

toyboot4etoyboot4e

緑 diff を埋めたが詰めが甘い

緑 diff streak 週間をやっていて、 ABC255 ~ ABC299 までの緑 diff の問題を埋めました。緑色まで上がるために十分な数の問題を解いたつもりです。


AtCoder Problems

しかし AC できるかと言われると 非常に厳しい 。考察成功率 9 割で実装力も足りていたつもりですが、なにかしらハマって AC できないという場合が多かったです。

先日の ABC 292 D もそうでした。なぜ DFS だと AC できないのか……? 実装を間違えているかコーナーケースを取り零しているはずですが、自力で脱出できないのです。

次にやること

緑 diff を AC できるようになる訓練は、 Haskell 盆栽の趣旨とは異なります。本来やるべきなのは、高度なモナドを使いまくったズル過ぎる解法なのですが、そういうものが存在するのかも怪しく、普通の競プロをやるしかありません。

ならば高度な DP や高度なグラフアルゴリズムを解けるようになるのが、もっとも盆栽的だと思います。それほどの腕力を付けたころには、緑 diff も完投できるはず。高校物理が分からないなら大学物理で整理すればいいじゃないの精神で EDPC などやっていきます。

toyboot4etoyboot4e

未だ Haskell 初心者

Reader モナドすら使ったことが無いのだ……! 人のコードを数千行読めばレベルアップすると思うのですが、いつものゲーム開発方面で良いコードを探したいですね。

Streak を切らさない程度に例のやつをを読みますか……!

https://github.com/patrickt/possession

toyboot4etoyboot4e

DeepL は僕より圧倒的に技術翻訳が上手い (比喩)

つまりそういうことか……!

toyboot4etoyboot4e

関係式を効率的な計算に書き直してくれる

うおおおおおおお! 遅延評価くん、再雇用するから戻ってきてくれー!!

https://twitter.com/naoya_ito/status/1636910624096800768

手直しは必要みたいですが、あと一歩じゃん……! コード生成には未来がありますね。こんな宣言型プログラミングは予想外でした。

toyboot4etoyboot4e

小数の入力をパースするには

ByteString には readDouble が無いので、一旦こちらで:

map (read @Double) . words <$> getLine

ByteString のパース用型クラスを追加してもいいかもしれません。

toyboot4etoyboot4e

実戦で E 問題を AC したぞ!

単なる配列のマージだったけれど…… (ナップサック問題をリストで解く方が難しい) 。 Haskeller としては有利な出題でした。 ABC 294 E

GPT くん……

GPT ペアプロは戦術
https://twitter.com/autotaker1984/status/1637451044048478208

DSL Python / DSL 遅延評価 Haskell, アセンブリ C++
https://twitter.com/autotaker1984/status/1637051659816947712

『実力』の幅が広がった
https://twitter.com/autotaker1984/status/1637051286226075650

toyboot4etoyboot4e

水パフォでも +73 かぁ

入水は相当遠いなぁ……

現在のレーティングでは、パフォーマンス 1,400 を 10 連発しないと水色に辿り着きません

toyboot4etoyboot4e

マイナ言語提出をざっと見 (ABC 294)

こうして見ると Haskeller は多いです。 Lisp 0 がショックで、 Fortran と OCaml が面白いですね (過疎だけれども……) 。

  • CommonLisp: 0. えっ?
  • D: つおい
  • Erlang / Elixir: 0.
  • Fortran: Haskell も使える人がいました。強いし面白い
  • Idris: そもそも使用不能。 2023 update で追加してほしいですが……
  • Kotlin: つおい
  • Nim: つおい
  • OCaml: 面白い
  • Octave: こたつがめ先生
  • Perl: こたつがめ先生
  • Prolog: 0
  • Racket: 0. えっ?
  • Scala: Haskell より少ない
  • Schene: 0. えっ?
  • Standard ML: 0.
  • Swift: Haskell より少ない
  • Text: これはなに……?
  • TypeScript: Haskell とタメ
  • Unlamba: 0
  • Vim: 実質 0
  • VB: つおい (本当に強い)
  • zig: 提出不能
  • zsh, bc, dc: 0
toyboot4etoyboot4e

ARC に参加するべきか

レーティングが茶色以下の場合、 ARC 1 問目が 300 点の場合は出ても良さそうです。緑になったら出ない方が無難かも……

toyboot4etoyboot4e

水色、というより水色になるための高度典型が欲しい

日報です。コンテンツのメモを書きます。

Youtube

あーだこーだー 73 回 (PAST 本上級編 の著者陣 + すべての黒幕):

https://www.youtube.com/watch?v=bpGkkpm9xs0

緑色以上の人は PAST 本 (上級編) を買え! とのこと。今週の ABC で緑色になるぞ!

本 (発売待ち)

3/27 に PAST 本上級編が発売します。難度的には鉄則本 < PAST 上級本 <= 蟻本 といった並びで、良い具合に間が埋まります。

内容としては、主に AtCoder の過去問を解説してくれる (あーだこーだーより) ようなので、ジャッジも利用可能です。これは楽しみです。

https://www.amazon.co.jp/アルゴリズム実技検定-公式テキスト[上級]~[エキスパート]編-大槻兼資/dp/4839979499

I use Kindle Scribe btw.

Podcast

Rebuild が来た! うおおお!

https://rebuild.fm/357/

機械と脳のハード的な違いは並列性にあるはずで、たかだか秒間数十億回の直列計算で人間の思考をエミュレートできてたまるか (TLE だろ JK) と思っていましたが、今何が起こっているのでしょうね? 機械のアブダクションで Millennium Prize Problems を解きたい……が、解けても 100 年待たないと人には証明が認められないかもしれません。 (取らぬたぬきの (取らぬたぬきの (皮算用)) ……

ところで naoya さんは Dijkstra と遅延評価 DP の人だと思っていましたし、 miyagawa さんは natsumeg さんのキリストという認識でしたが、氷山の一角でした。僕も情報量の多い人間になりたい〜

toyboot4etoyboot4e

Lang test 2023

まだまだコンパイルエラーが直ってないようですが、次期環境を試すことができます:

https://atcoder.jp/contests/language-test-202301

  • ghc9.9.4! 僕のテンプレートは無修正で動いてくれました。
  • Zig がいる!
  • Idris がおらぬ!
  • Racket, OCaml, Standard ML がいないんですが、消えるわけではないですよね……?
toyboot4etoyboot4e

区間 DP (O(N^3))

写経して AC. 初の青 diff でした。

https://atcoder.jp/contests/tessoku-book/tasks/typical90_s

https://twitter.com/e869120/status/1384638694162780166/photo/1

解き方はこういう感じですか:

区間長 5 の計算には区間長 2 ~ 4 を用い、区間長 4 の計算には区間長 2 ~ 3 を用い、……。無効な計算を番兵で弾くのですが、オーバーフローしないように値を選ぶ必要があったりと煩雑です。もっと明示的な計算に切り替えたいところです。

あーだこーだーでは、鉄則本は初心者向けに特化しているという見方でしたが、初心者、初心者……?

toyboot4etoyboot4e

類題 (TDPC I)

これは解けませんでした。ステップに工夫が必要です。

https://atcoder.jp/contests/tdpc/tasks/tdpc_iwi

バッファ

  • 2 次元配列 dp に区間 [l, r] から取り除くことができる iwi の数を保存します
    • succ (r - l) == 3 * dp ! (l, r) の場合、その区間の文字を削除することができます

ステップ

  • 遷移 1: 区間 [l, m] と区間 [succ m, r] の DP をマージする
  • 遷移 2: l, [succ l, pred m], m, [succ m, pred r], r に分けて考える
    • 区間 [l, r] を完全削除できる場合は、以下を満たします:
      • 区間 [succ l, pred m] が全削除可能かつ区間 [scc m, pred r] が全削除可能
        • なお長さ 0 の区間は削除可能であるとする
      • l, m, r を繋げると i, w, i になる
    • 全削除できない場合は、サブ区間で最大の削除文字数を dp ! (l, r) に記録します

瞬殺されておる……!

この解答、 2ms だと……?! しかもリストを使った解答です。一体何が起きているのか……! (読まないけれど圧倒的な差を感じます)

https://atcoder.jp/contests/tdpc/submissions/6523971

toyboot4etoyboot4e

再帰関数がメンタルモデルにハマらない

平易な問題を解くのにもハックを使っています。まともな Haskell を書けているのか心配です。

例: fold (入力のループ処理)

たとえば foldl' の結果をリストに蓄えた場合、出来上がったリストは FILO となり、 reverse しなければキューのようには扱えません。

foldl' の替わりに再帰関数を使えば、 FIFO なリストを作ることもできます:

結果は FIFO
f (x:xs) = g x : f xs

でも僕が書きたいのは foldl' なんです! 今は reverse していますが、何かしっくりくるループの道具が欲しいところです。

foldl' の代替

foldl' が合わない場合は、 mapAccumL とか State + mapMMaybe を返せば良い気がしてきました。

  • そのループは状態を持つことができる
  • そのループは入力を map / filter できる
  • そのループの出力は FIFO である
  • Break はできない

なお break は定数倍の高速化なので、コンテストでは必要ありません。

再帰関数で入力処理

再帰関数では再帰を止めれば処理を打ち切れるので、 break できます。なら普段から再帰を使っておけば良いもののですが、 fold の方が『私はこういう者です』と伝えてくれる気がしていて、中々再帰関数に馴染めません。

toyboot4etoyboot4e

昔と環境が変わった?

僕が bit DP と区間 DP を習得したら、この記事で言うところの水色コーダーの条件を満たしてしまう気がするのですが、そんなわけないですよね……?

https://qiita.com/e869120/items/eb50fdaece12be418faa#2-3-分野別初中級者が解くべき過去問精選-100-問

当時の水色は今の緑後半ぐらいではという気がしてしまいます。 ABC 8 問体制の記事が欲しいところです。

toyboot4etoyboot4e

2 次元配列の traceShow (改良版)

map ではなく foldl' にしないと traceShow が評価されませんでした。わりと汚い……

traceMat2D :: (IArray a e, Ix i, Show e) => a (i, i) e -> ()
traceMat2D !mat =
  let !_ = foldl' step () (range ys)
   in ()
  where
    ((!y0, !x0), (!yEnd, !xEnd)) = bounds mat
    !xs = (y0, yEnd)
    !ys = (x0, xEnd)
    step !_ !y = traceShow (map (\ !x -> mat ! (y, x)) (range xs)) ()
toyboot4etoyboot4e

PAST 上級本メモ

鉄則本より高度な内容で緑以上の人におすすめだとか。まだ茶色ですが、ざっと読んでエアプします。

6 月の PAST も受けてみようかな。

https://book.mynavi.jp/bookstore-info_books_detail/id=135840

1. 2 分探索 [Done]

2 分探索の良問は解けるようになりたい……!

  • 食塩水 (平均濃度の最大化)

    • モンスターが魔力袋に見えるか!
    • 2 分探索の判定問題を『濃度を X 以上にできるか』と設定する
    • 平均の不等式は積の不等式であり、各項からの寄与の和の不等式に変形できる
    • \sum_i k_i x_i >= X \Leftrightarrow \sum_i (k_i - X x_i) >= 0
    • よって判定問題は O(N) で計算できる (ソートするので O(N log(N) か……)
    • 判定問題の中でさらに 2 分探索させられるやつとかもある……!
  • 区間長の最小化

    • 判定問題を greedy に解くことができる
    • Yokan Party と同様のはず?
  • 平均値・中央値の最大化

    • ABC 236 E は特殊な制約が強い
    • まず DP を考え、次元を減らす
    • 平均値だけでも十分難しい

2. DP

DP のパタンは実は多くない、とか言いつつ最も分厚い章です。 最近解いた EDPC のおかげで既知の内容が多かった ことにびっくり……!

fold 型 [done]

1 つずつ入力を受け取って状態を進めます。ナップサック問題など。よくテーブルで説明されますが、 fold で解けます (配列 → 配列 を繰り返す) ので僕は fold 型と呼んでいます。

  • 全探索が a^n になる問題は DP の可能性が高い
    先日の ABC295D は例外ですね…… (敗北)

部分問題で工夫が必要な場合

  • 区間 DP (1) dp[r]

    • 全探索は O(2^{n-1}).
    • dp[i]i 番目に仕切りを入れる場合のコストとする
    • dp[i] への遷移は i 通り
    • 事前処理はセグ木 (区間木) を使ってしまうとチートできる気がします。
  • 2 系列 (編集距離)

    • 2 系列の添字を進めていく
    • 遷移は左側から 1 文字捨てる、右側から 1 文字捨てる、両文字が一致するの 3 通り
    • これは鉄則本のテーブルの説明が一番分かりやすいかな。
  • 区間 DP (2) dp[l][r]

    • [l, r] の全サブ区間を DP 配列に入れる (dp[l][r])
    • 長さ 1 のサブ区間、長さ 2 のサブ区間、 .. の順で求める
    • 新しい区間はサブ区間をマージして埋めるか、左右の要素を足す形で新しい区間を作る
    • O(N^3). 特にイウイ難しいよなぁ……
  • 集合 DP (bit DP)

    • 巡回セールスマン問題

部分問題の高速化テクニック

  • 部分問題に DP 配列の累積和が必要な問題

    • 謎の TLE に見えるので要注意
    • ロングジャンプ。なるほど……。和の範囲は尺取り方またはにぶたんで
    • EDPC M など入力値が幅を取れるタイプのナップサック問題
      • このぐらいなら fold で十分 (テーブル不要)
    • 区間 DP + 累積和の場合はメモ化 DP では解けないんですよね
    • ABC 183 E 難し過ぎる‥‥という程でもないのが凄い
  • 行列累乗

WIP

3. 頻出テクニック

  • 変数を固定する
    変数を 1 つずつ決めていくと、残りの変数が O(N)O(log N) で求まったりする。商列挙もそうですね。ループで 0 番目を決めるのも固定の一種だと思います。要素が 3 つある場合など難し目の典型もあり。

  • 尺取り法
    尺取り法も単調性に依存するため、尺取り法で解ける問題は 2 分探索やセグメント木で大体解けるんですよね。なるべく使わないのが競技的には良いかなと思っています。 XOR sum もセグ木の 2 分探索で解けるんじゃないかな……?

4. 頻出データ構造/アルゴリズム

5. ネットワークフロー

6. セグ木

7. セグ木 DP

読まないかも

8. 平面走査

読まないかも

9. チャレンジ

読まないかも

toyboot4etoyboot4e

小説の読み上げが良い

競プロに合う Youtube/Podcast を切らしていたのですが、小説読み上げ (n 倍速) が良かったです。「ぐあっ!?」が『ぐ』『あ』『疑問符』になったりしますけれども意外と問題なし!

toyboot4etoyboot4e

期待値の DP をどう解こう [DONE]

DP では数え上げが典型ですが、期待値をもとめよと言われたらどう解くか……?

https://atcoder.jp/contests/dp/tasks/dp_j

解けぬ……!

コイン (定義に従って)

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

定義に従って:

E[X] = \sum_i p(x_i) x_i = \sum_{n=1}^{\infty} \frac n {2^n} = 2

解析的には解けましたが、コンテスト向きではありません……

コイン (DP で)

同じ問題を DP で解いてみます。普通に状態遷移を考えるとこうです:

この図の最終遷移先 (表の数 1 ) で同様に状態遷移を考えると、実は DP 的な期待値の捉え方が現れます。 dp_ii 回表を出すまでの試行回数の期待値として:

すなわち dp_1 = \frac 1 2 (dp_0 + 1) + (1 - \frac 1 2) (dp_1 + 1) 。なんで dp_i + 1 なん……? という部分を素通りすれば (なんでだ……) 式は期待値の定義通りです。ここで dp_0 = 0 (表を 0 回出すまでに必要な試行回数は 0) を代入して dp_1 = 2

これは競プロ外でも普通に使えるじゃん……! 数学 A でやった記憶もありません。なんでこれで解けるんだ……

不動点?とか、上位の知識があればもっと見通しが良くなるのでしょうが。

Sushi

Sushi に戻ると、遷移先の状態から遷移元の状態へ逆向きに期待値が流れ込むように見て:

dp_{i_1, i_2, i_3} = \frac {i_0} {n} (1 + dp_{i_1, i_2, i_3}) + \frac {i_1} {n} (1 + dp_{i_1 - 1, i_2, i_3}) + \frac {i_2} {n} (1 + dp_{i_1 - 1, i_2 + 1, i_3}) + \frac {i_3} {n} (1 + dp_{i_1, i_2 - 1, i_3 + 1})

等式変形:

\begin{align} dp_{i_1, i_2, i_3} &= 1 + \frac {i_0} {n} (dp_{i_1, i_2, i_3}) + \frac {i_1} {n} (dp_{i_1 - 1, i_2, i_3}) + \frac {i_2} {n} (dp_{i_1 + 1, i_2 - 1, i_3}) + \frac {i_3} {n} (dp_{i_1, i_2 + 1, i_3 - 1}) \\ (1 - \frac {i_0} {n}) dp_{i_1, i_2, i_3} &= 1 + \frac {i_1} {n} (dp_{i_1 - 1, i_2, i_3}) + \frac {i_2} {n} (dp_{i_1 + 1, i_2 - 1, i_3}) + \frac {i_3} {n} (dp_{i_1, i_2 + 1, i_3 - 1}) \\ dp_{i_1, i_2, i_3} &= \frac {1} {(1 - \frac {i_0} {n})} (1 + \frac {i_1} {n} (dp_{i_1 - 1, i_2, i_3}) + \frac {i_2} {n} (dp_{i_1 + 1, i_2 - 1, i_3}) + \frac {i_3} {n} (dp_{i_1, i_2 + 1, i_3 - 1})) \end{align}

よし、これで解いてみます。

参考

この記事が最も助けになりました:

https://qiita.com/ys_dirard/items/bb16dbb7fa88fd8e4c09

TODO

https://compro.tsutaj.com//archive/180220_probability_dp.pdf

toyboot4etoyboot4e

類題: ABC 280 E

まあ Sushi より簡単です! mod の割り算が出てくる点で diff が上がっているかも。

flip fix でメモ化再帰便利だな〜

toyboot4etoyboot4e

類題: ABC 263 E

上の 2 問と同様に、あるいは Nyaan 氏の解説 の通り、遷移先の期待値を元に遷移元の期待値を計算できる謎の技法で AC.. できず TLE しました。

なぜ TLE するのか、対策は

もしや N が大きいとメモ化 DP が通用しない……? 遅延評価はしていないので大丈夫だと思いたいですが。

ああ部分問題が O(N) になっていますね。 DP 配列の累積和が欲しい! となるとメモ化 DP のスタート地点と反対側、最終到着点からループすることで、依存される DP 値をすべて確定させておく必要があります。 flip fix も短い相棒でした 。遅延評価と同じ運命を辿ります……。

AC!

他の問題もメモ化無しで解き直すべき

TODO

toyboot4etoyboot4e

Bit DP (集合を添字とする DP) [DONE]

ゲーム問題から逃げつつ、新たな典型 DP へ。次の ABC で DP 出してくれないかな……!

https://atcoder.jp/contests/dp/tasks/dp_o

考察

場合の和だけ数えて DP できんな〜と悩んでいました。色々な解説を読んで閃いたこととしては、ペア確定済みの女性の集合 (Int = bit 列) の集合 (IntMap) を状態に持って、 i 番目の男性を入力とし fold するだけで済みそうです。

TODO: 遷移を詰めて考える。 concatMap しつつ filter するだけで良い?

提出

IntMap を fold して TLE しました。 concatMapfold に置き換えてもダメ! なんか sparse な fold は通らないことが多いですね……

Vector でも TLE です。。確かに O(2^N N^2) になりますね。入力処理の考え方だと計算量を減らせないかもしれません。

状態のループで再挑戦

AC! 全く自力で解けた気がしない 。案外今までの DP で最難関かもしれません。

toyboot4etoyboot4e

類題: ABC 274 E

Bit DP 用の状況整理をサクっとこなす必要があります。

  • グラフは行列形式で持つ
  • 原点 (0, 0) を追加する
toyboot4etoyboot4e

類題を解けるようになる方法。それは……

類題を解くことではないでしょうか (白目)

1 問解けば他の 5 問も解けるようになる、なんて上手い話は無さそうです (僕のスペックの場合) 。

toyboot4etoyboot4e

Early return が無くても戦えるようになってきた

return, break, continue が無くても構わない。なぜなら 5 段階ぐらいのネストには耐性ができてしまったから……!()

Rust に戻った際は、自分の生み出した { } の山を慌てて修正し勘を戻しました。

toyboot4etoyboot4e

逆向きの range

[i1 .. i2] の記法は enumFromTo に desugaring されます。enumFromToi1 <= i2 の場合のみリストの要素を生成します:

i> [0..3]
[0,1,2,3]

> [3..0]
[]

[i1, i2 .. i3] の記法は enumFromThenTo に desugaring され、ステップ値を考えて配列を生成します:

i> [0, 2 .. 5]
[0,2,4]

> [0, -1 .. -5]
[0,-1,-2,-3,-4,-5]

[0 .. pred n] を逆順にイテレートするなら、 [pred n, pred (pred n), .. 0] と書くことになりそうです。ちょっと使いにくいですね。

https://stackoverflow.com/a/31753727

toyboot4etoyboot4e

fold 型 DP でバッファが 2 次元の場合

たとえば ABC 281 D. 類題をサクッと実装できるようにノートしておきます。

昔解けた問題でも考察に時間がかかってヒヤヒヤします。とにかく考察に弱い。 ABC 全問再考察とかやるべきかもしれません……。

Vector で 2 次元のバッファを表す

準備:

let undef = -1 :: Int
input <- getLineIntVec

step 関数の添字を 2 次元にするだけ良くて、たしかこんな感じ:

let bounds = ((0, 0), (pred k, pred d))
let result = VU.foldl' step s0 input
    s0 = VU.generate (\case 0 -> 0; _ -> undef) (rangeSize bounds_)
    step vec x = VU.generate (\ikid -> f (ikid `quotRem` d)) (rangeSize bounds_)
      where
        f (ik, id) = undefined

UArray を使うには

Vectorgenerate に相当するのは listArray かと思います。おそらくこんな感じ:

let result = VU.foldl' step s0 input
    s0 = listArray @UArray bounds_ (\case (0, 0) -> 0; _ -> undef)
    step arr = listArray bounds_ f
      where
        f (ik, id) = undefined
toyboot4etoyboot4e

未消化の典型メモ

まず視野に入れねば、とメモだけ……

解けたい典型

  • 辞書順
    • next / prev permutation
    • S は辞書順で何番目 (転倒数と似ていて区間木で実装)
    • 辞書順で n番目の文字列は何か
  • 2 次元グリッドの座標圧縮
    さっと解説を読んで理解できないのはやはり頭が悪いのか……?
    https://pione.hatenablog.com/entry/2021/03/03/060451
  • ☆ 食塩水
    • ABC034D: 完全に理解した!
    • ABC294F: 判定問題をどう高速化どうすればいいんだ…… (絶望)
      → 数え上げの 2 分探索で (N log N). 基礎能力が問われる
  • 転倒数
    考察ができるかは別の話
  • Rolling hash
  • 2-SAT
  • LCA
  • 二部マッチング
    • 重み最大化一般マッチング
  • 最大流量
    • 最小費用流
    • 最小カット
  • Floor min (格子点の数)
  • 主客転倒
  • 燃やす埋める
  • 包除原理 (激ムズ……)
  • 中国剰余定理 (CRT)
  • 3 分探索
  • 拡張ユークリッドの互助法

避けている問題

  • ゲーム
    • Nim ゲーム
    • Grundy 数

高度典型

直帰の ABC で出たらしいですが影も拝めていない問題たち:

https://ei1333.github.io/luzhiled/

toyboot4etoyboot4e

完全敗北 orz

ABC296, 手も足も出なかったよ……! 正直、近々緑まで行けると思っていたのですが、完全に停滞コースですね。

それはそれとして天才なので典型を押さえていく……!

toyboot4etoyboot4e

Upsolve

done. D は全探索の範囲絞り込み (a <= b とすれば計算量が \sqrt M まで減るのが難しい) 、 E は読解力、 F は考察が分からないので Qiita の解説待ちです。

F: 公式解説で理解

書いてある通りだ……!

反転数の偶奇は互換操作(特定の 2 つの要素を選んで入れ替える操作) によって必ず反転します。

高橋君の行う操作では、1 回の操作で A,B に対してちょうど一度ずつ互換操作を行なっているため、A,B の反転数の総和の偶奇は高橋君の操作によって変化しません。

A,B が一致している時、反転数も等しいですから、その和は偶数となります。よって、最初の時点で反転数の総和が奇数である場合は一致させる事ができません。

問題タイトルの同時スワップは、初期状態で偶奇が不一致に場合、永遠に一致しないことを示している、また同じ要素が 2 つある場合、片側のみ反転数の偶奇が変化しない操作が可能と。

初期状態で偶奇が一致している場合は必ず一致させられる証明は一旦飛ばしています。 ABC って意味深だが無意味な制約で惑わしてくることが多いですよね。

E: scc (強連結成分) の手頃な実装が欲しい

ってなんじゃこりゃぁ! Haskell 好き過ぎじゃ!

https://atcoder.jp/contests/abc296/submissions/40275678

toyboot4etoyboot4e

D のような数え上げのパターンの命名

何もやる気がしないときにこたつがめ先生を見ると、知性の強度に感化されて元気が出ますよ! sed 瞬殺は中二的にかっこいいなぁ……

https://m.youtube.com/watch?v=6yrddain6ns&feature=youtu.be

D では 2 つの約数のうち小さい方を列挙する、ということで『小列挙』と呼んでいるのが天才! この呼び名は検索にも出ないので完全に造語だと思います。

D 問題は『小列挙』で探索の範囲を sqrt サイズにする問題だったと思うことにします。

約数問題、その典型解法とは

小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙昇竜拳小列挙小列挙小列挙小列挙小列挙小列挙小列挙小列挙

toyboot4etoyboot4e

転倒数 (inversion number)

ぐっ……典型!

転倒数とは

バブルソートに必要な swap の回数 (隣接要素の swap 回数)

転倒数の偶奇

偶奇が一致しているなら ∞ 回の操作の後に列を一致させられる、みたいな問題が多いようです。応用は類題を解いて身につけるしか……!

Segment tree (区間木) による実装

PrimMonad 実装にしておいて良かったです:

-- | Calculates the inversion number. Input must be in range [0, n - 1]
invNumVU :: VU.Vector Int -> Int
invNumVU xs = runST $ do
  let !n = VU.length xs
  !stree <- newSTree (+) n (0 :: Int)

  -- NOTE: foldM is better for performance
  !ss <- VU.forM xs $ \x -> do
    -- count pre-inserted numbers bigger than this:
    !s <-
      if x == pred n
        then return 0
        else querySTree stree (succ x, pred n)

    -- let !_ = traceShow (x, s, (succ x, pred n)) ()
    modifySTree stree succ x
    return s

  return $ VU.sum ss

Binary index tree / Fenwick tree

TODO. 上の区間木実装で十分な気がします。

問題

toyboot4etoyboot4e

グラフテンプレート強化

Data.Graph があまりにも使いにくいので、やはり自作する必要があります。

方針

トポロジカルソートや SCC ではグラフの全頂点を訪問します。効率を考えて、探索済み頂点は (IntMap ではなく) MVector に保存します。 MVector の方が実装も簡単になると思います。

実装

SCC からループ成分を抽出する

SCC の結果において、長さ 2 以上の頂点は常にループ (相互に到達可能) です。長さ 1 の頂点は、自己ループもしくはループではないです。

Data.Graph の SCC はその点を考慮して SCC = AsyclicSCC v | CyclicSCC v となっていますね。

ループ成分を抽出する関数を sccCycles としました。 これにて ABC 296 E を AC!:

sccCycles :: Array Int [Int] -> [[Int]]
sccCycles graph = filter f $ scc graph
  where
    -- self-referencial loop only
    f [!v] = v `elem` (graph ! v)
    f !_ = True

まだまだテストしていきます。

toyboot4etoyboot4e

自作 scc のさらなるテスト

最近、半年来の区間木のバグに気付きました。 scc は信頼できるものであってほしい……!

Functional graph

SCC でループを検出できます。 scc に慣れていると Union-Find 以上に簡単になります:

もっと複雑なもの

AtCoder Tags で 探してみました。

  • ABC 245 F
    余事象で答えようとして WA. どの問題でも基礎の不足を感じます。

  • 2-SAT? (とは)

toyboot4etoyboot4e

org-roam 始めました

org-roam-ui がグラフを表示してくれます:

  • ノード = ファイル (.org)
  • 辺 = リンク ([[<file-id>][<title>]] 形式のもの)

内容が充実したら公開したいと思います。 公開している人 もいますね。

toyboot4etoyboot4e

日本語で解説を書いてみる

怪しい日本語の練習になります。早速間違った解説を書いてしまいました……。

正しく直す訓練だと思うことにします。

toyboot4etoyboot4e

Haskell は AtCoder ガチ言語だったかもしれない

天下の Common Lisp はスタックのサイズから四苦八苦しているようです。言語アップデートも無し。隣の芝生が枯れて見えますよ……!

https://competitive12.blogspot.com/2020/03/common-lisp.html

ヒューリスティックで使ってみたかったのですが、 Haskell 以上に無謀な気がします。また半年這いつくばる勇気は無いかもです……。

でもこんなの買い始めたらたまらんだろうな〜……

https://www.amazon.co.jp/Programming-Algorithms-Lisp-Efficient-Programs-ebook/dp/B08VD28D6D/ref=sr_1_24?adgrpid=53725520616&hvadid=618574579089&hvdev=t&hvlocphy=1009530&hvnetw=g&hvqmt=e&hvrand=1128129626481447406&hvtargid=kwd-301742569655&hydadcr=23464_13568778&jp-ad-ap=0&keywords=practical+common+lisp&qid=1680602414&sr=8-24

toyboot4etoyboot4e

たとえば AtCoder 向きではない言語

最近 AtCoder に上陸した Emacs Lisp とかですかね……!

https://atcoder.jp/contests/language-test-202301/submissions/40386158

1 行目の set-buffer が異彩を放っており、周りの言語がネイティブ実行される中、テキストエディタと融合しています。 exit は kill-emacs ですよ。いよっ、 Lisp エイリアン!

僕の知る限り一番難しいプログラミング言語は batch ファイルですが、 Emacs Lisp もなかなかですよね……。

toyboot4etoyboot4e

V や Zig もやってくる!

どちらも C の単ファイルにトランスパイルして実行できるはずですが、ネイティブ環境があるのは良さそうです。

toyboot4etoyboot4e

基本は変わらない (n 敗)

全探索をベースに、ちょっと効率の良い部分問題の解法を考えられたら、大抵の問題は解けるんじゃないでしょうか (敗北) 。

基本を変えない

一生灰のままコンスタントな成果を出し続けるのも戦略としてはアリだと思います。全然面白くないけれど……

toyboot4etoyboot4e

再帰関数 vs foldr vs reverse $ foldl' ..

リストで FIFO をやる場合、どれが最も効率が良いか。

https://tobi462.hatenablog.com/entry/2016/05/08/222251

TODO

map の 3 通りの実装

上記リンクからパクってきます。

  1. 再帰関数
map' :: (a -> b) -> [a] -> [b]
map' !_ [] = []
map' !f (!x:xs) = f x : map' f xs
  1. foldr
map'' :: (a -> b) -> [a] -> [b]
map'' !f = foldr (\ !x !acc -> f x : acc) []
  1. reverse $ foldl'
map''' :: (a -> b) -> [a] -> [b]
map''' = reverse $ foldl' (\ !acc !x -> f x : acc) []
toyboot4etoyboot4e

ModInt p [Done]

繰り返し二乗法で mod を取る処理を自動化したいです。

※ 繰り返し二乗法は相当重いので、繰り返し二情報が不要なアルゴリズムで ModInt を使うと TLE になる危険性があります。たとえば rolling hash.

https://yukikurage.hatenablog.com/entry/2021/04/22/213645

ModInt p を作ろう

ModInt のフィールドに法 (modulus) を入れるよりは、 ModInt 型自体に法の値を含ませたいです。理由は、

  • 異なる法の ModInt 同士を計算できないようにしたい
  • Num クラスの fromInteger :: Integer -> ModInt を実装したい

そのため ModInt p 型を作り、 p 型から法の値が得られるようにしたいです。

p を素数に限定できると尚良いですが。

定数を型で渡す (Proxy)

m 型から値を得る関数とは f::<T>() に相当します。 Haskell の関数は第一に引数から多相のパラメータを推論するため、このように引数 0 の多相関数を作ることはできません。替わりに f (Proxy @a) のように関数 fProxy a の値を与えることで、間接的に型パラメータ a を渡すことができます。

定数を表す class を作ってみます:

-- | Type level constant `Int` value.
class TypeInt a where
  typeInt :: Proxy a -> Int

使い方:

data MyModulus = MyModulus

instance TypeInt MyModulus where
  typeInt _ = 1_000_000_007

typeInt (Proxy @MyModulus)
-- => 1000000007

ModInt MyModulus のコンストラクタも追加しておきます:

modInt :: Int -> ModInt MyModulus
modInt = ModInt

https://zenn.dev/mod_poppo/books/haskell-type-level-programming/viewer/phantom-types-and-proxy

TODO: TypeNats

先程の TypeInt ですが、 GHC のモジュールに TypeNats があるので置き換えます。

https://hackage.haskell.org/package/base-4.18.0.0/docs/GHC-TypeNats.html

Show インスタンスの実装

そのまま解答に利用できるよう、 IntShow に委譲しました:

-- | `Int` where modulus operation is performed automatically.
newtype ModInt p = ModInt { toInt :: Int }
  deriving (Eq)

instance Show (ModInt p) where
  show = show . toInt

Num クラスの実装 (+, * など)

ModInt pNum クラスを実装してきます。演算心の実装はスムーズに進むと思います。

Num のメンバはなmだっけ? instance Class Type where を書いた時点で、言語サーバのコードアクションにより関数の placeholder を生成可能です。

instance TypeInt p => Num (ModInt p) where
  (ModInt !x1) + (ModInt !x2) = ModInt $ (x1 + x2) `mod` typeInt (Proxy @p)
  (ModInt !x1) * (ModInt !x2) = ModInt $ (x1 * x2) `mod` typeInt (Proxy @p)
  negate (ModInt !v) = ModInt $ (-v) `mod` typeInt (Proxy @p)
  abs = id
  signum _ = 1
  fromInteger = ModInt . fromInteger

Fracitonal クラスの実装 (/ など)

ModInt pFractional クラスを実装します。

instance TypeInt m => Fractional (ModInt p) where
  -- reciprocal of x (inverse of x)
  recip (ModInt !x) = ModInt $ invModF x (typeInt (Proxy @p))
  fromRational !r = ModInt n / ModInt d where
    n = fromInteger $ Ratio.numerator r
    d = fromInteger $ Ratio.denominator r

invModF は繰り返し二乗法と素数 mod (確か Fermet's little theorem) で実装されています。

toyboot4etoyboot4e

Debug-only な traceShow [DONE]

traceShow を使うと stderr 出力で printf デバッグができます。しかし print が長いと TLE を起こすため、提出前に確実にコメントアウトしなければなりません。

CPP 言語拡張

CPP 言語拡張を有効化すると #ifdef が書けます。 #ifdef を使えば、提出時には確実に traceShow を消去できます:

-- {{{ Debug-only utilities

#ifdef DEBUG
dbg :: Show a => a -> ()
dbg !x = let !_ = traceShow x () in ()

#else
dbg :: Show a => a -> ()
dbg = const ()

#endif

-- }}}

DEBUG 属性を有効化する方法ですが、stack の引数に --ghc-options "-D DEBUG" を渡せば有効化できると思います。

Stack script として DEBUG 属性を有効化する

Stack script の場合はコメントで stack への引数が書けるので、 define を追加しました:

#!/usr/bin/env stack
{- stack script --resolver lts-16.31
--package array --package bytestring --package containers --package extra

--ghc-options "-D DEBUG"
-}

ローカルでは stack script として実行されるため DEBUG 属性が有効化され、ジャッジの際は無効になります。

toyboot4etoyboot4e

Debug-only な assertion

Haskell にも assert 関数があります:

https://qiita.com/mod_poppo/items/b3b415ea72eee210d222

1. Control.Exception.assert を使う

Assertion に使った式が表示できない、と元の記事にもありました。まあこれで十分だと思います。

  • エラー関数表示
  • エラー行表示

Haskell のコンパイルコマンドは ghc -o a.out -O2 {dirname}/{filename} なので、 -fignore-asserts は指定されていません。ローカルで有効、ジャッジでは無効な asset を作るには、やはり DEBUG フラグを使おうと思います。

2. CPP 言語拡張でマクロを作って式表示

こちらは assertion に使った式も表示できそうですが、 , を式に含められないなど構文に制限がかかるようです。

3. Template Haskell で assertion

これはやめておきましょう……

toyboot4etoyboot4e

Rolling Hash [Done]

文字列 → 数値列 → B 進数 (素数 mod) 累積和 → スライス時に桁を調整

データ型の追加

ローリングハッシュは必要なデータ数と計算ステップが多いためバグりやすいです。オブジェクト指向っぽく集約と隠蔽で問題を分解しましょう。

案外実戦でも、データ型を作った方が手戻りが無くて速いかもしれません。盆栽力も実装力の内。そして盆栽は得意のはずなのだ……!

  • ModInt
    mod 計算を隠して実装を簡単にします。 そうか、繰り返し二乗法は使わない方が 10 倍以上はやくなりますね……! ロリハに ModInt は使わない!

  • RollingHash b p
    B^n 列や累積和など、データを 1 箇所に集約できれば簡単に使えるはずです。

  • HashSlice
    ハッシュ値と合わせて長さを記憶しておけば、スライス同士の連結ができます。

    • consHashSlice
    • catHashSlices

TLE 対策

繰り返し二乗法を使わない実装にしましょう!!! (重要)

演習

Rolling Hash - AtCoder Tags

  • ABC 284 F
    繰り返し二乗法の rolling hash で TLE, 位取りの方法を見直して AC!
toyboot4etoyboot4e

わしのテンプレートは 1,500 行あるぞ

ふぉっふぉっふぉ (古いやつは消さないと……)

ロードも困難なほど長いソースを提出する人たちもいる

サーバ攻撃かと思いましたが、他コンテストにおける撃墜対策なのかも? 何にせよ良いものではなさそうです。

toyboot4etoyboot4e

1 次元の MVector に対する 2 次元の view [TODO]

添字が二次元 (Int, Int) なグラフの探索済み頂点を MVector に収納したい!

1 次元の MVectorVector (MVector Bool) へと変換するコードを見つけました:

vis <- mLinToMat n n <$> VUM.replicate (n*n) False

しかし実装を追うのは難しい…:

IL(mLinToMat) :: (VGM.MVector v a) => Int -> Int -> v s a -> V.Vector (v s a)
mLinToMat h w lvec = vEvalElemsId $ V.generate h (\i -> VGM.slice (i*w) w lvec)

IL(vEvalElemsId) :: (VG.Vector v a) => v a -> v a
vEvalElemsId = vMapFoldl (\ !_ !x -> (x,())) ()

IL(vMapFoldl) :: (VG.Vector v b, VG.Vector v c) =>
  (a -> b -> (c,a)) -> a -> v b -> v c
vMapFoldl f a
  = VG.unstream . VFB.inplace (streamMapFoldl f a) id . VG.stream

TODO? (Array なら Ix をそのまま使えるしなぁ)

https://atcoder.jp/contests/abc289/submissions/38803986

toyboot4etoyboot4e

Idris 2 を垣間見るか [WIP]

正格評価がデフォルトな Haskell として Idris2 を C にトランスパイルしてコード提出してみたい! まずは NixOS で環境構築してみます。

失敗するかもですが。

Idris について

Idris1 の本があります。

https://zenn.dev/blackenedgold/books/introduction-to-idris

I use Kindle Scribe btw.. のコーナーです。

ところで僕は Type-Driven Development with Idris を買って Kindle Scribe で 25% 程度読んでいます。これはマウントポイントです。みなさん Kindle Scribe は持っていない確率が高いですからね。 Remarkable 派は、国内で見たことが無いですが、密かにライバルと認識しています。

idris2 0.6

configuration.nixidris2 パッケージを追加してインストールできます。ただ idris2 自体 pack (idris2-pack) についてくるようなので、 configuration.nix から入れないほうが良いかもしれません。

idris2-pack

グローバルインストールは NixOS では上手く行かない気がするのですが、やってみます。

$ nix-shell -p gmp
> bash -c "$(curl -fsSL https://raw.githubusercontent.com/stefan-hoeck/idris2-pack/main/install.bash)"

もし成功したらパスを通したりパッケージを追加したり……

Package Collection  : nightly-230408
Idris2 URL          : https://github.com/idris-lang/Idris2
Idris2 Version      : 0.6.0
Idris2 Commit       : a75161cb2021136d4c87e65f25a1d4bc50a7279f
Scheme Executable   : scheme
Pack Commit         : 6ff0f1ac46fd0d8ad13b827cb96ece7187730d8d
Installed Libraries : base
                      contrib
                      idris2
                      linear
                      network
                      prelude
                      test

成功しました。動くかな

エディタサポート

idris2-lsp

$ pack install-app lsp

Emacs では idris2-mode.. ではなく idris-mode を使った方が良さそうですね。

ひとまず昔の設定を有効化して……

(leaf idris-mode
    :mode "\\.l?idr\\'"
    :hook lsp-deferred
    :custom
    (idris-interpreter-path . "idris2")
    :after lsp-mode
    :config
    (add-to-list 'lsp-language-id-configuration '(idris-mode . "idris2"))

    ;; (with-eval-after-load 'lsp-mode ;; LSP support for tlp-mode
    (lsp-register-client
     (make-lsp-client
      :new-connection (lsp-stdio-connection "idris2-lsp")
      :major-modes '(idris-mode)
      :server-id 'idris2-lsp)))

ipkg

言語サーバを動かすためには .ipkg ファイルが必要だとか。 チュートリアルリファレンス を参照しつつ……

$ mkdir try-idris2
$ cd try-idris2
$ git init
$ idris2 --init255 main
Package name: try-idris2
Package authors: toyboot4e
Package options:
Source directory: src
$ ls
try-idris2.ipkg
$ cat try-idris2.ipkg
try-idris2.ipkg
package try-idris2
-- version =
authors = "toyboot4e"
-- maintainers =
-- license =
-- brief =
-- readme =
-- homepage =
-- sourceloc =
-- bugtracker =

-- the Idris2 version required (e.g. langversion >= 0.5.1)
-- langversion

-- packages to add to search path
-- depends =

-- modules to install
-- modules =

-- main file (i.e. file to load at REPL)
-- main =

-- name of executable
-- executable =
-- opts =
sourcedir = "src"
-- builddir =
-- outputdir =

-- script to run before building
-- prebuild =

-- script to run after building
-- postbuild =

-- script to run after building, before installing
-- preinstall =

-- script to run after installing
-- postinstall =

-- script to run before cleaning
-- preclean =

-- script to run after cleaning
-- postclean =
$ mkdir src
$ cat <<EOS > src/Main.idr
main :: IO ()
main = "Hallo"
EOS

エディタで開くとあっさりエラー表示が出ました。 main 関数の前に、 Haskell で言う module Main where みたいなパッケージ宣言が必要なのでしょう。幸先良いですね。

実行など試していきます。

toyboot4etoyboot4e

WIP

先程のエラーは main :: IO ()main : IO () に修正して解消されました。

REPL から実行する

競プロ勢は言語を問わず REPL からソース実行している気がします。それが基本ということでしょうか。

$ idris2 src/Main.idr
:exec main
"Hello"$

なるほど "Hello" になっていますね。 putS からの C-nputStrLn に補完……

補完してほしい

してほしかった。設定不足かキーバインド不足か未実装なのか……

プロジェクトの実行ファイルをビルド・実行する

cargo run はどこ…… (Idris 界隈は Make 派が多かったっけ) 。

  • pack -o

C バックエンドを使いたい (1)

使い方を調べたくないチョップ (・ω)))) > > > >

これで一応ビルドできるかな

$ nix-shell -p gmp --command 'idris2 --cg refc src/Main.idr -o c-main'
$ ls build/exec
c-main*  c-main.c  c-main.o  _tmpchez*  _tmpchez_app/

Print するだけのプログラムが 570 行。依存が増えたらどうなるか分かりませんが、提出・実行してみましょう。

words 関数が無い……?

分かりました (言ってみたいだけ) 。

module Main

import Data.String

partial
main : IO ()
main = do
  [a] <- map cast . words <$> getLine
  [b, c] <- map cast . words <$> getLine
  print $ a + b + c
  getLine >>= print

でトランスパイル結果が外部ファイル (runtime.hidris_support.h とその実装) に依存しているため実行不能と……。 embed <ファイル名> とか standalone で検索していくしかありません。

C バックエンドを使いたい (2)

使い方を調べたくないチョップ (・ω)))) > > > >

C の単ファイル生成方法を調べるか考えます。

toyboot4etoyboot4e

私の戦闘力は 777 です

ABC 5 完でした。考察成功! 言語によっては Set に deleteMinFind が無いようなので大変そうです。意外と Haskell の方が死角が無いのかもしれない……

知識が活かされる問題でも無かったけれど、今後も典型盆栽をやっていきます。高度典型で高パフォーマンスを狙いたい。

toyboot4etoyboot4e

WIP: 箱詰め問題、主客転倒、包除原理

ABC 297 F を解くぞ!

用語

  • 包除原理

    • 集合が 2 つの場合
      線形な f に対して f(X) = f(A) + f(B) - f(A \cap B) (ただしA \cup B = X)
    • 一般の場合
      TODO
  • 主客転倒
    \sum_i f_i(\{ x_j \}_j) = \sum_j g(x_j) ? 整理中

ABC 290 E

包助原理から逃げて主客転倒をやりました。 TLE の回避も必要な良問です。こういう問題が解けると良いのですけれどね。

ABC 003 D

まずは簡単な類題から。紙の上ですら全然分からん……!

https://soohprogramming.wordpress.com/2020/10/03/abc003-d-atcoder社の冬/

ABC 297 F

toyboot4etoyboot4e

MVectorMArray の累積和を求める\には

fold で済む問題なら scanl' で済む話ですが、どうしても可変配列が必要な場合に備えてモナディックな累積和取得を考えたいです。 forM_ を回すのが意外としんどい……

……と思いきや 1 行で済みそうですね。

VU.foldl' (+) (0 :: Int) <$> VU.mapM (VUM.read dp) (vRange 0 (vLength dp - 1))
toyboot4etoyboot4e

基本 MVector を使うべきではない

2 次元の DP でも scanl' で解けることは多く、 immutable な Vector の累積和は簡単に計算できます。まずは MVector を避けられるか考えるべきです。

toyboot4etoyboot4e

人のテンプレートを読む

黒魔術 Haskell を避けて通れば、素直で良さそうなコードが目に入ります。

  • Vector の stream の使い方
  • unwordsByteStringBuilder
  • 純粋関数版・モナディック関数版の実装の統一 (2 分探索など)
    純粋関数版を runIdentity $ モナディック版 として実装します

Haskell すきー氏のコードから見ていくぞ!

toyboot4etoyboot4e

コードリーディング #1

人のコードを貼りまくって権利的に問題無いでしょうか。

https://atcoder.jp/contests/abc298/submissions/40693065

Stream

vector の stream は配列に特化していて異常に速いらしいです。生の vector を使っても stream fusion により実行結果は変わらないの かも しれませんが、イテレータのメンタルモデルを強調できて良い気がします。

まずは import:

import qualified Data.Vector.Fusion.Bundle.Monadic as MB
import qualified Data.Vector.Fusion.Bundle.Size    as MB
import qualified Data.Vector.Fusion.Stream.Monadic as MS

Stream で範囲を表します:

stream :: (Monad m) => Int -> Int -> MS.Stream m Int
stream !l !r = MS.Stream step l where { step x | x < r = return $ MS.Yield x (x + 1) | otherwise = return MS.Done; {-# INLINE [0] step #-}}
{-# INLINE [1] stream #-}

INLINE [1] の記法は inline pragma の phase 指定らしいです。特に stream を扱う場合は重要そうですが未読です。

https://qiita.com/ruicc/items/4c33fe2501fa956cd9a5

逆向きの stream:

-# INLINE [1] stream #-}
streamR :: (Monad m) => Int -> Int -> MS.Stream m Int
streamR !l !r = MS.Stream step (r - 1) where { step x | x >= l = return $ MS.Yield x (x - 1) | otherwise = return MS.Done; {-# INLINE [0] step #-}}
{-# INLINE [1] streamR #-}

Delta 値を指定できる stream:

stream' :: (Monad m) => Int -> Int -> Int -> MS.Stream m Int
stream' !l !r !d = MS.Stream step l where { step x | x < r = return $ MS.Yield x (x + d) | otherwise = return MS.Done; {-# INLINE [0] step #-}}
{-# INLINE [1] stream' #-}
rep :: (Monad m) => Int -> (Int -> m ()) -> m ()
rep n = flip MS.mapM_ (stream 0 n)
{-# INLINE rep #-}

rep1 :: (Monad m) => Int -> (Int -> m ()) -> m ()
rep1 n = flip MS.mapM_ (stream 1 (n + 1))
{-# INLINE rep1 #-}

rev :: (Monad m) => Int -> (Int -> m ()) -> m ()
rev n = flip MS.mapM_ (streamR 0 n)
{-# INLINE rev #-}

rev1 :: (Monad m) => Int -> (Int -> m ()) -> m ()
rev1 n = flip MS.mapM_ (streamR 1 (n + 1))
{-# INLINE rev1 #-}

Shift

これは全く読めない:

infixl 8 `shiftRL`, `unsafeShiftRL`
shiftRL :: Int -> Int -> Int
shiftRL = unsafeShiftRL
{-# INLINE shiftRL #-}
unsafeShiftRL :: Int -> Int -> Int
unsafeShiftRL (I# x#) (I# i#) = I# (uncheckedIShiftRL# x# i#)
{-# INLINE unsafeShiftRL #-}

ByteStringBuilder

改行区切りで vector を出力:

unlinesB :: (G.Vector v a) => (a -> B.Builder) -> v a -> B.Builder
unlinesB f = G.foldr' ((<>) . (<> endlB) . f) mempty

空白区切りで:

unwordsB :: (G.Vector v a) => (a -> B.Builder) -> v a -> B.Builder
unwordsB f vec | G.null vec = mempty | otherwise = f (G.head vec) <> G.foldr' ((<>) . (B.char7 ' ' <>) . f) mempty (G.tail vec)

Vector のみならず、 Foldable 全般に実装してみたいです。それとも Vector に限定したほうが高速になるのでしょうか。ガバベンチを作りたい。

連結:

concatB :: (G.Vector v a) => (a -> B.Builder) -> v a -> B.Builder
concatB f = G.foldr ((<>) . f) mempty

mconcat ではなく?

特化した BSB (行列 (各行は空白区切り), グリッド (各行は単なる concat), 長さ付き vector):

matrixB :: (G.Vector v a) => Int -> Int -> (a -> B.Builder) -> v a -> B.Builder
matrixB h w f mat = F.foldMap ((<> endlB) . unwordsB f) [G.slice (i * w) w mat | i <- [0 .. h - 1]]

gridB :: (G.Vector v a) => Int -> Int -> (a -> B.Builder) -> v a -> B.Builder
gridB h w f mat = F.foldMap ((<> endlB) . concatB f) [G.slice (i * w) w mat | i <- [0 .. h - 1]]

sizedB :: (G.Vector v a) => (v a -> B.Builder) -> v a -> B.Builder
sizedB f vec = B.intDec (G.length vec) <> endlB <> f vec

Yes / No:

yesnoB :: Bool -> B.Builder
yesnoB = bool (B.string7 "No") (B.string7 "Yes")

Char7 は ASCII, Char8 は UTF-8 らしいです。どうせ ascii 文字しか使わないので string8 より string7 の方が良さそうです。

uncurry (<>) . bimap 的なもの:

pairB :: (a -> B.Builder) -> (b -> B.Builder) -> (a, b) -> B.Builder
pairB f g (x, y) = f x <> B.char7 ' ' <> g y

StringBuilder:

showB :: (Show a) => a -> B.Builder
showB = B.string7 . show

showLnB :: (Show a) => a -> B.Builder
showLnB = B.string7 . flip shows "\n"

shows は知らなかった。 flip show "\n"\x -> show x ++ "\n" と等価になりそうです。 ++ よりも効率が良かったりするのでしょうか。

改行に名前を付けておくのは良いですね:

endlB :: B.Builder
endlB = B.char7 '\n'
{-# INLINE endlB #-}

出力:

putBuilder :: (MonadIO m) => B.Builder -> m ()
putBuilder = liftIO . B.hPutBuilder stdout

putBuilderLn :: (MonadIO m) => B.Builder -> m ()
putBuilderLn b = putBuilder b *> putBuilder (B.char7 '\n')

MonadIOIO またはその合成のようです。

座標関係? [TODO]

これは何だろう:

neighbor4 :: (Applicative f) => Int -> Int -> Int -> (Int -> f ()) -> f ()
neighbor4 h w xy f = when (x /= 0) (f $ xy - w) *> when (y /= 0) (f $ xy - 1) *> when (y /= w - 1) (f $ xy + 1) *> when (x /= h - 1) (f $ xy + w) where { (!x, !y) = quotRem xy w}
{-# INLINE neighbor4 #-}

2 分探索

モナディックな 2 分探索。必ず境界の左側の添字を返してるっぽい:

binarySearchM :: (Monad m) => Int -> Int -> (Int -> m Bool) -> m Int
binarySearchM low0 high0 p = go low0 high0 where { go !low !high | high <= low = return high | otherwise = p mid >>= bool (go (mid + 1) high) (go low mid) where { mid = low + unsafeShiftRL (high - low) 1}}
{-# INLINE binarySearchM #-}

なぜ unsafeShiftRL なのか? 保留……

これは興奮しましたが純粋関数はモナディック関数の特殊版なんですね:

binarySearch :: Int -> Int -> (Int -> Bool) -> Int
binarySearch low high p = runIdentity $ binarySearchM low high (return . p)
{-# INLINE binarySearch #-}

境界の左側を取るのが lowerBound, 右側を取るのが upperBound ということですね。単調減少の場合は逆になるのかもですが:

lowerBound :: (Ord a, G.Vector v a) => v a -> a -> Int
lowerBound !vec !key = binarySearch 0 (G.length vec) ((key <=) . G.unsafeIndex vec)
{-# INLINE lowerBound #-}

upperBound :: (Ord a, G.Vector v a) => v a -> a -> Int
upperBound !vec !key = binarySearch 0 (G.length vec) ((key <) . G.unsafeIndex vec)
{-# INLINE upperBound #-}

Encode? [TODO]

基数ソートとは?:

radixSort :: U.Vector Int -> U.Vector Int
radixSort v0 = F.foldl' step v0 [0, 16, 32, 48] where { mask k x = unsafeShiftRL x k .&. 65535; step v k = U.create $ do { pos <- UM.unsafeNew 65537; UM.set pos 0; U.forM_ v $ \ x -> do { UM.unsafeModify pos (+ 1) (mask k x + 1)}; rep 65535 $ \ i -> do { fi <- UM.unsafeRead pos i; UM.unsafeModify pos (+ fi) (i + 1)}; res <- UM.unsafeNew $ U.length v; U.forM_ v $ \ x -> do { let { !masked = mask k x}; i <- UM.unsafeRead pos masked; UM.unsafeWrite pos masked $ i + 1; UM.unsafeWrite res i x}; return res}}
{-# INLINE radixSort #-}

何の話だろう:

encode32x2 :: Int -> Int -> Int
encode32x2 x y = unsafeShiftL x 32 .|. y
{-# INLINE encode32x2 #-}
decode32x2 :: Int -> (Int, Int)
decode32x2 xy = let { !x = unsafeShiftRL xy 32; !y = xy .&. 4294967295} in (x, y)
{-# INLINE decode32x2 #-}

パーサ関係? [TODO]

未読:

uvectorN :: (U.Unbox a) => Int -> PrimParser a -> PrimParser (U.Vector a)
uvectorN = vectorN
{-# INLINE uvectorN #-}

bvectorN :: Int -> PrimParser a -> PrimParser (V.Vector a)
bvectorN = vectorN
{-# INLINE bvectorN #-}

vectorN :: (G.Vector v a) => Int -> PrimParser a -> PrimParser (v a)
vectorN n f = do { (e, o) <- viewPrimParser; pure $ G.unfoldrN n (pure . runPrimParser f e) o}
{-# INLINE vectorN #-}

runSolver :: (a -> IO ()) -> PrimParser a -> IO ()
runSolver = withInputHandle stdin

PrimParser [TODO]

これは何だろう。入出力の最適化は必ず効くためか、気合が入った効率化と抽象化が両立されている予感がします]:

newtype PrimParser a = PrimParser{runPrimParser# :: Addr# -> Addr# -> (# Addr#, a #)}

PrimParser を返す関数

xyz <- (,,) <$> int <*> int <*> int のように、パーサの組み合わせができたはずです。上手い。

int :: PrimParser Int
int = PrimParser $ \ e p -> case runPrimParser# intP e p of { (# p', x #) -> (# plusAddr# p' 1#, x #)}
{-# INLINE int #-}

uint :: PrimParser Int
uint = PrimParser $ \ _ p -> case wordP# p 0## of { (# p', x #) -> (# plusAddr# p' 1#, I# (word2Int# x) #)}
{-# INLINE uint #-}

uint1 :: PrimParser Int
uint1 = PrimParser $ \ _ p -> case wordP# p 0## of { (# p', x #) -> (# plusAddr# p' 1#, I# (word2Int# x -# 1#) #)}
{-# INLINE uint1 #-}

double :: PrimParser Double
double = PrimParser $ \ e p -> case runPrimParser# doubleP e p of { (# p', x #) -> (# plusAddr# p' 1#, x #)}
{-# INLINE double #-}

char :: PrimParser Char
char = PrimParser $ \ _ p -> case indexWord8OffAddr# p 0# of { w8 -> (# plusAddr# p 1#, B.w2c (W8# w8) #)}
{-# INLINE char #-}

など。

IntMod, ガロア域? [TODO]

これは一体どういうことだッ

type IntMod = GF 998244353

何が起きているッ

newtype GF (p :: Nat) = GF{unGF :: Int} deriving (Eq)
                                        deriving newtype (Read, Show)

何だというのだッッッッッッ

instance (KnownNat p) => Num (GF p) where { (GF# x#) + (GF# y#) = case x# +# y# of { xy# -> GF# (xy# -# ((xy# >=# m#) *# m#))} where { !(I# m#) = natValAsInt (Proxy @p)}; {-# INLINE (+) #-}; (GF# x#) - (GF# y#) = case x# -# y# of { xy# -> GF# (xy# +# ((xy# <# 0#) *# m#))} where { !(I# m#) = natValAsInt (Proxy @p)}; {-# INLINE (-) #-}; (GF# x#) * (GF# y#) = case timesWord# (int2Word# x#) (int2Word# y#) of { z# -> case timesWord2# z# im# of { (# q#, _ #) -> case minusWord# z# (timesWord# q# m#) of { v# | isTrue# (geWord# v# m#) -> GF# (word2Int# (plusWord# v# m#)) | otherwise -> GF# (word2Int# v#)}}} where { !(W# m#) = natValAsWord (Proxy @p); im# = plusWord# (quotWord# 18446744073709551615## m#) 1##}; {-# INLINE (*) #-}; abs = id; signum = const (GF 1); fromInteger x = GF . fromIntegral $ x `mod` m where { m = natVal (Proxy @p)}}
toyboot4etoyboot4e

コードリーディング 1: 感想

とても素直で親切なコードである気がしました。逆に言うと、これほど親切な実装にも黒魔術は憑き物であり、異質な Haskell 筋が必要です。

主な発見

  • 純粋関数はモナディック関数の特殊版である
  • パーサが上手い
    xyc <- (,,) <$> int <*> int <*> int1 のように組み合わせられる (この際 1-based index から 0-based index への変換もできてしまう。上手い)
  • 定形の出力を簡単に出せるようになっている
  • 75% ぐらいのコードは表層しか読めない
toyboot4etoyboot4e

コードリーディング 2

Haskeller にはレーティング差以上の実力差を感じます。この人も Haskell 縛りじゃなければとっくに入水してそうですね。

スタイルはリストと array の正統派 Haskell という感じです。最小限のテンプレートは常にコードに入れていて、参考にするには理想的な様子です。

https://atcoder.jp/contests/abc298/submissions/40697228

ModInt

法はグローバル変数:

newtype ModInt = ModInt { getModInt :: Int }
    deriving (Eq, Ord, Show)

modInt :: Int -> ModInt
modInt !x = ModInt (x `mod` modulus)
 
modulus :: Int
modulus = 998244353

instance Num ModInt whereinstance Fractional ModInt where

Applicative 的な:

liftMod :: (Int -> Int) -> ModInt -> ModInt
liftMod op (ModInt !x) = modInt $ op x
 
liftMod2 :: (Int -> Int -> Int) -> ModInt -> ModInt -> ModInt
liftMod2 op (ModInt !x) (ModInt !y) = modInt $ op x y

というか僕も applicative を ModInt に実装すべきか!

Mod 上の逆数

TODO: 拡張ユークリッドの互助法とは:

modInverse :: Int -> Int -> Int
-- the value y which satisfies
--   (x * y) `mod` k == g where g = gcd x k.
-- when k is a prime number and 0 < x && x < k, g == 1.
modInverse k x = (`mod` k) $ fst3 $ extGcd x k
 
extGcd :: Int -> Int -> (Int, Int, Int)
-- the triple (p, q, g) which satisfies
--   p * x + q * y == g where g = gcd x y.
-- requires x >= 0 && y >= 0.
extGcd x y
    | y == 0    = (1, 0, x)
    | otherwise = (q, p - q * d, g)
    where
        (d, m) = divMod x y
        (p, q, g) = extGcd y m

Array の tabulate

遅延評価で DP をやるためのテンプレート? たぶん Richard Bird 先生のアルゴリズム設計本に載っているやつと同種のものだと思います:

tabulate :: (Ix i, A.IArray a e) => (i, i) -> (i -> e) -> a i e
tabulate bnds f = A.listArray bnds $ f <$> range bnds
 
fixArray :: Ix i => (i, i) -> (A.Array i e -> i -> e) -> A.Array i e
fixArray bnds f = tab
    where
        g = f tab
        tab = tabulate bnds g

入力処理 [TODO]

StateT でパーサを作るパターンを理解していない:

class Readable a where
 
    readBS :: B.ByteString -> Maybe (a, B.ByteString)
    readBS = runStateT readBSState
 
    readBSState :: StateT B.ByteString Maybe a
    readBSState = StateT readBS

関数ではなく型ベースでパース?:

instance Readable Int where
    readBS = B.readInt . dropSpace
 
instance Readable Char where
    readBS = B.uncons . dropSpace

-- など

よくある操作に明示的な名前を:

discardLine :: IO ()
discardLine = void B.getLine

出力 (ByteStringBuilder)

この辺はみんな同じかもしれませんね:

class Renderable a where
    renderBS :: a -> BB.Builder
instance Renderable Int where
    renderBS = BB.intDec

-- 略

Interactive 問題で必須のやつ:

flush :: IO ()
flush = hFlush stdout
toyboot4etoyboot4e

コードリーディング 2: 感想

強い。

  • Array の遅延評価で解いてる?!
  • 基本 array だとしたら、いつ Vector を抜くのだろう
  • 拡張ユークリッドの互助法で mod 世界の逆数が求められるらしい?
  • StateT でパーサを作るパターンが優れている気がする
  • やはり入力処理が上手い (ちょっとズルいレベル):
    [n, a, b, p, q] <- getFromLine
toyboot4etoyboot4e

コードリーディング 3

短いので全部貼ります:

https://atcoder.jp/contests/abc298/submissions/40666842

{-# LANGUAGE MultiWayIf #-}
import Data.Array.IArray
import Data.Maybe
import Data.Ratio
import qualified Data.ByteString.Char8 as C

main :: IO ()
main = do
    [n, a, b, p, q] <- getInts
    let dp = fArray ((1,1), (n,n)) f :: Array (Int, Int) MInt
        pqinv = 1 / (fromIntegral p * fromIntegral q)
        f (i,j) = sum os * pqinv where
            os = do
                ra <- [1..p]
                rb <- [1..q]
                let i' = min n (i + ra)
                    j' = min n (j + rb)
                if | i' == n   -> pure 1
                   | j' == n   -> pure 0
                   | otherwise -> pure $ dp!(i',j')
    print $ unMInt $ dp!(a,b)

getInts :: IO [Int]
getInts = map (fst . fromJust . C.readInt) . C.words <$> C.getLine

----------

mm :: Int
mm = 998244353

newtype MInt = MInt { unMInt :: Int } deriving (Eq, Ord, Show)

instance Num MInt where
    MInt a + MInt b = MInt $ let c = a + b in if c >= mm then c - mm else c
    MInt a - MInt b = MInt $ let c = a - b in if c < 0 then c + mm else c
    MInt a * MInt b = MInt $ a * b `mod` mm
    abs             = id
    signum          = MInt . signum . unMInt
    fromInteger     = MInt . fromInteger . (`mod` fromIntegral mm)

instance Fractional MInt where
    recip          = (^(mm - 2))
    fromRational r = fromIntegral (numerator r) / fromIntegral (denominator r)

----------

fArray :: (IArray a e, Ix i) => (i, i) -> (i -> e) -> a i e
fArray b f = listArray b (f <$> range b)
{-# INLINE fArray #-}
toyboot4etoyboot4e

コードリーディング 3: 感想

こちらも遅延評価で解いています。 いやそんな簡単そうな問題だったか ……?? Richard Bird ルートです。僕の Haskell はこの方向性を目指していません。成績を順当に上げる目的ならば、真似しようとしない方が良いと思っています。強い。

toyboot4etoyboot4e

高難度解説 AC vs 適正難度演習

ABC 258 B で詰まって実力不足を感じています。水 diff の解説 AC ばかりではなく、茶 ~ 緑の問題を大量に解くべきか……?!

https://atcoder.jp/contests/abc258/tasks/abc258_b

いずれは全問解くのだから、順番は問題ではない気もします。なら盆栽優先で!

toyboot4etoyboot4e

環境の危機

nixos-rebuild switch しなければ良かったです (・ω ))

oj

こちらは stable 版を使って問題を回避できます。 Unstable 版も誰かに直してもらわなければ……

lxml が無い?
[INFO] load cookie from: /home/tbm/.local/share/online-judge-tools/cookie.jar
[NETWORK] GET: https://atcoder.jp/contests/agc001/submit
[NETWORK] 200 OK
[ERROR] failed to check the login status: Couldn't find a tree builder with the features you requested: lxml. Do you need to install a parser library?
Traceback (most recent call last):
  File "/nix/store/4dsd891ki6p3lg2jq6zhb9c2vanfssrr-python3.10-online-judge-tools-11.5.1/lib/python3.10/site-packages/onlinejudge_command/subcommand/submit.py", line 88, in run
    is_logged_in = problem.get_service().is_logged_in(session=sess)
  File "/nix/store/z5f20zgfr0b8jx0vy8hccmwzzgdsj1h6-python3.10-online-judge-api-client-10.10.1/lib/python3.10/site-packages/onlinejudge/service/atcoder.py", line 101, in is_logged_in
    resp = _request('GET', url, session=session, allow_redirects=False)
  File "/nix/store/z5f20zgfr0b8jx0vy8hccmwzzgdsj1h6-python3.10-online-judge-api-client-10.10.1/lib/python3.10/site-packages/onlinejudge/service/atcoder.py", line 56, in _request
    _list_alert(resp, print_=True)
  File "/nix/store/z5f20zgfr0b8jx0vy8hccmwzzgdsj1h6-python3.10-online-judge-api-client-10.10.1/lib/python3.10/site-packages/onlinejudge/service/atcoder.py", line 38, in _list_alert
    soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.HTML_PARSER)
  File "/nix/store/z7lniafa29cg42q8rdbbj1xv48dl9zzd-python3.10-beautifulsoup4-4.11.2/lib/python3.10/site-packages/bs4/__init__.py", line 248, in __init__
    raise FeatureNotFound(
bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: lxml. Do you need to install a parser library?

HLS

HLS が死ぬ

  • わしのテンプレートはデカ過ぎたらしい
    フォッフォッフォ (コメントアウトで HLS が動くようになりました)

  • どこまでテンプレートを削れば良いのか
    伝家の宝刀 † 二分探索 † ですぞ!

  • derivingUnbox で死んでいた
    なぜ…… orz

今日限りの対処法

フォーマットだけでも使いたいので、 derivingUnbox がコメントアウトされた状態でエディタを起動することにしました。致し方なし……

toyboot4etoyboot4e

Union-Find を改良すべき?

ABC 264 E の実装が重めだったので、ライブラリの不足を感じています。多少パフォーマンスが落ちても汎用的に改造すべきでしょうか。

https://atcoder.jp/contests/abc264/tasks/abc264_e

欲しい機能

グループに所属する要素の数を覚える

ルートの頂点番号 → グループ内の要素数

僕の union-find にもこの機能がありました。 API を追加しよう……

-- | `MUFChild parent | MUFRoot size`.
data MUFNode = MUFChild {-# UNPACK #-} !Int | MUFRoot {-# UNPACK #-} !Int

API もあった。。記憶が風化していますね。

グループごとに payload を載せる

ルートの頂点番号 → payload

いずれも unite 処理の拡張である

まあ毎回配列を作ってクロージャで unite すれば競技的には十分か……!

toyboot4etoyboot4e

TODO: IO のガバベンチ

ByteString で読むと速い?

Vector に読むと速い?

ByteStringBuilder で出すと速い?

  • <> すべきか、都度書き出すべきか
toyboot4etoyboot4e

PAST 上級の夢を見た

実際解いてみたら TLE しまくりだよ……!

https://atcoder.jp/contests/past202109-open

PAST の配点

序盤移行は 1 問 6 点です。

  • 中級 (60 点以上): 6 問まで間違えてよい
  • 上級 (80 点以上): 3 問まで間違えてよい
  • エキスパート (90 点以上): 1 問のみ間違えてよい
toyboot4etoyboot4e

問題です

次のループ処理のうち、 TLE の危機に有るコードはどれですか?

  1. (!! n) $ iterate f s0
  2. snd $ until ((== n) . fst) (bimap succ f) (0, s0)
  3. foldl' step (\x _ -> f x) [1 .. n]
  4. VU.foldl' step (\x _ -> f x) (VU.replicate n False)

解答

  1. iterate: TLE
  2. until: 684 ms
  3. foldl': 632 ms
  4. VU.foldl': 626 ms

学び

ループ処理に iterate を使うのは止めよう……

toyboot4etoyboot4e

TLE が取れた && WA が取れた

天才かよ……! 正直茶 diff 感あります。謎のハマり方をするのが僕なんですね?

5 時間あったら本当に上級を狙えそうです。

toyboot4etoyboot4e

上級の実力は無かった

第八回 PAST を 11 問 AC でギブアップ。 Upsolve していきます。

尺取り法

バグりまくったのでテンプレートを書きました。条件を満たす区間を返します:

twoPointers :: Int -> ((Int, Int) -> Bool) -> [(Int, Int)]
twoPointers !n !check = inner (0, 0)
  where
    inner (!l, !r) | l >= n = []
    inner (!l, !r)
      | check (l, r) =
          let (l', r') = until (not . peekCheck) (second succ) (l, r)
           in (l', r') : inner (succ l', max l' r')
      | otherwise = inner (succ l, max (succ l) r)
    peekCheck (!l, !r) | r == pred n = False
    peekCheck (!l, !r) = check (l, succ r)

大分無駄が多いのですが、今は定数倍の遅延を許容します。シュッとした Haskell は読めないし書けない。。

toyboot4etoyboot4e

Pascal ユーザの人の記事

おお……。 Pascal ユーザは Basic ユーザより少ない可能性があります。果たしてその実体は?

しかしながらPascalメインで使っている人は皆無に等しく
昨日のコンテストABC287で提出結果を検索してみると自分以外では
セルビアの人が1人だけで参加者約10000人弱中たった2名のみという超不人気言語です。

面白すぎるw

https://qiita.com/kenken2go/items/b697e712914a67cf552b

AtCoder の UI を作るという手があったか……!

https://qiita.com/kenken2go/items/c123c1cd47586eb40381

めちゃめちゃ面白い。やはりマイナ言語周辺はコンテンツ価値が高いようですね。

toyboot4etoyboot4e

LCA (Lowest Common Ancestor: 最小共通祖先)

最低共通祖先と言ったほうがイメージが湧きやすい。サイテー!

       . CA
       |
       . CA
       |
       * LCA
      / \
 v1' .   * v2
    /
v1 *

木が非常に深い場合を考えてダブリングによる高速化が必要

概要

  1. DFS などで depths (頂点 → 深さ), parents (頂点 → 親頂点) を求める
  2. depths をダブリングする
    深さを 1 つ戻る関数は配列で表現できる。配列の出力を配列に繋ぐことでダブリングができる。よって深さ d 戻る処理を (大きな) 定数時間で求められる。
  3. v1, v2 の深さを揃える (上図で v1 → v1')
    この操作は無くても良いが説明が簡単になる
  4. 戻る深さ d に関する 2 分探索で LCA が求まる
    深さ d 戻った頂点が共通祖先であるかは d に関して短調 (上図からも一応イメージできそう)

ダブリングの実装に関して

配列の生成と配列の適用に分けました:

newDoubling :: (VG.Vector v a, VG.Vector v Int) => a -> (a -> a) -> v a
newDoubling !x0 !squareF = VG.scanl' step x0 $ VG.enumFromN (0 :: Int) 62
  where
    step !acc !_ = squareF acc

applyDoubling :: (VG.Vector v a) => v a -> b -> (b -> a -> b) -> Int -> b
applyDoubling !doubling !x0 !f !n = foldl' step x0 [0 .. 62]
  where
    !_ = dbgAssert $ VG.length doubling == 63
    step !acc !nBit =
      if testBit n nBit
        then f acc (doubling VG.! nBit)
        else acc

ダブリングされた関数 (関数適用のための値) のことを doubling と呼ぶのはセンスが悪い気はしています。 chopped とかもう少し良い名前に変えたいところですが……

dbgAssert
dbgAssert :: Bool -> a -> a
dbgAssert False !x = error "assertion failed!"
dbgAssert True !x = x

#else
dbgAssert :: Bool -> a -> a
dbgAssert = flip const

#endif

ダブリングの概念に関して

具体例は、

  • 繰り返し 2 乗法
    かける数をダブリングしておく

  • LCA
    配列の出力を配列に繋いでダブリングする

イメージとしては、合成関数 (の元になる値) を事前計算できるものがダブリング可能なもの? もっと的確に捉えたいが……

応用

  • 木の上の 2 点間の距離が分かる (LCA からの距離の和)
    ただ辺に重みがある場合は LCA では無理そう

演習

AtCoder Tags

  • ABC 209 D
    具体的な距離を知りたければ安易に LCA, 偶奇を知るだけなら2部グラフとして色分け。なるほど。

  • ABC 126 D
    length root v1 + length root v2 = length v1 v2 + 2 * const. のため 2 頂点間の偶奇が分かる。 LCA は使用しないが、根を基準に考察する点では確かに同じ分類の問題かも。

  • ABC 267 F
    明日やろう。

toyboot4etoyboot4e

木の探索

  • 同じ頂点を 2 度訪問しない
    隣接頂点の取得時に親のみ除外する (実装が楽)

  • 2 頂点の距離

    • 色分け (偶奇)
    • 距離 = LCA からの距離の和
    • 根からの距離
toyboot4etoyboot4e

TODO: グラフ関連のコードを抽象化したい

グラフ関連のテンプレートが肥大化しています。グラフの特徴を抽出して整理したいところですが、全く目処が立ちません。検討中……

参考にしよう

https://qiita.com/hossie/items/ff0e9be89f22dea41aea

グラフの特徴

  • 有向グラフ、無向グラフ
  • 木、 functional graph, 複雑なもの
  • 連結か否か
  • 重み付きグラフ
    • 重みの型
  • 負の辺の有無
  • 頂点の payload
  • 辺の payload
  • etc.

個々の操作

  • 隣接頂点を取得
  • 隣接頂点を取得 (訪問済み頂点を除外する)

探索方法

  • 探索済み頂点を Vector で管理する
  • 探索済み頂点を IntSet で管理する
toyboot4etoyboot4e

Mex

集合に含まれない最小の非不整数を求めよ。言い換えれば、非不整数の集合の余集合の最小値を求めよ。〜水 diff の典型のようです。

演習

どっちも解けませんでした。

  • ABC 194 E
    配列を長さ M の窓で見たとき、すべての窓から得られる Mex の内、最小の値を求めよ。窓の中の数の集合の余集合を管理すると、余集合の中の最小値がその窓の mex となります。これが緑 diff なのは、やはり自頭の良い参加者の割合が高いということでしょうね。
  • ABC 272 E
    今度は値の範囲が O(10^9) 以上であるため、余集合が持てません。座標圧縮……しようにも、 + 1 した値を圧縮対象に入れるのは大変そうです。
    解説を見ました。考察も実装の大分つらい。 AtCoder は考察重視とは何だったのかと思うことが最近は多いです。飛ばすか……
    寝たら解けました。
toyboot4etoyboot4e

手続型 Haskell のコツだと思っていること

絶大な効果

  • 手続き型を避ける方法を考える。たとえば
    • 経路の収集と処理を分離する。収集は純粋関数にできる (maxFlow)
    • 候補を IntSet で持っておき、制約との差分を取って絞り込む (ABC 299 E, なるほど)
    • など

バグが減る

  • undef のような定数を作りマジックナンバーを避ける
  • イテレーション時の if よりもイテレーション前の filter (ネストが減る)
    より宣言的なコードへ近づいていく不思議

再帰の手段

  • 再帰関数をクロージャにして可変配列にアクセスする (と楽)
    let で関数定義して <- で実行となる
  • flip fix で再起関数を作成、即実行
    <- flip fix の 1 文で済む (それ以上の利点は無い)

迷っている点

  • forM の出力を一時変数に入れて、次の行でリスト処理
    • つまり xs_ <- forM .. の後に map f xs_ などとする
    • もし xs <- map f <$> forM .. とするならラムダ式を () で囲む必要がある
    • () で囲んだ方が良い可能性もある
    • そもそもこんな forM を書くよりも純粋なロジックに落とし込んだ方が良い
toyboot4etoyboot4e

TODO: 考察フローチャート

水 diff までなら機械的に考察できないものか。逆引き辞典をループするだけで得点できるようにしてみたいと思います。

以下ループして計算量を削減します

考察一般
  • クエリを逆順に処理する
  • グラフで表現する
    • トポロジカルソートで依存関係を整理
    • 最大流問題に帰結?
  • よく分からないクエリを brute-force 2 分探索判定で強行突破
  • 主客転倒
    • 全体を操作するのではなく、個々からの寄与で全体を求める方法を考える
  • 転倒数の偶奇によって完了状態に到達可能かが判定できないか
  • 操作前後の不変量を考察する
全探索
  • Brute-force 全探索
  • 全組み合わせを調べる
    • 商列挙 (変数の固定)
    • ソートして二分探索
    • 座標圧縮の後ソートして 2 分探索
    • 累積和をソートして尺取法で条件を満たす組み合わせをカウント
  • 境界を 2 分探索
  • 尺取法
  • 答えで二分探索
    • 最も大きな値を求めよ
    • 最も小さな値を求めよ
    • n 番目の値を求めよ
    • 不等式を変形して各項からの寄与の和に関する判定問題に変形
      • さらに brute-force 全探索、からの 2 分探索など
  • ダブリングしておく (置換操作など)
グラフ考察
  • グラフの問題であろうと、単なる入力処理と状態発展 (fold) で解けないか。
  • グラフを 2 つに分けて持つ
  • 状態を頂点に、遷移を辺としたグラフを考える
    • 頂点のタプルを状態とする
  • 1 頂点からの距離 (BFS, Dijkstra)
  • 1 頂点への距離 (逆グラフで BFS, Dijkstra)
  • 全各点間の距離を事前計算
    • 木なら LCA
    • 重み無しグラフなら全点から BFS
    • 正の辺のみなら Dijkstra
    • 負の辺があれば Floyd-Warshall?
  • 木の 2 点間距離の偶奇
    • 木を二部グラフと見て色塗り
    • 根からの距離の差の偶奇
    • LCA で求めた距離の偶奇
連結成分
  • Union-Find で連結成分を記録・更新する

    • 辺削除を避けて辺追加で解答する
  • トポロジカルソートしてみる

  • 木 DP (配る DP)

  • 木 DP (再帰関数による受け取る DP)

  • 強連結成分

    • 閉路を集める、閉路を解消する
    • 強連結成分で配る DP
DP
  • 集合 DP (行列形式グラフ、小さい集合から配る DP)
  • 区間 DP (短い区間から順に処理する、区間の和または区間の拡張で最適値を求める)
  • 桁 DP (忘れた)

随時更新予定

そんなにパターンは多くないので、詰まった時に発想を補うことができれば AC できる確率が上がります。その程度は既に GPT くんがやってくれそうですが…… (hey GPT, この問題の典型は? 類題は何?)

toyboot4etoyboot4e

絶望がデフォルト

解けない → 解説 AC できない → 誰も俺を愛さない → 寝る → 解ける

今日の自分は明日の自分の贄となることしかできない。。

toyboot4etoyboot4e

ヒューリスヒック エアプ

連休の自由研究でビームを飛ばすことにしました。ヒューリスヒックもやって行きましょう!

AHC

  • 月 1 の開催
  • 4 時間のコンテスト
  • 提出間隔は 30 分
  • レーティングは上昇のみ (マイナスが無い)

提出回数が限定されるので、ローカルでテスト環境を作ってパラメータの調整がしたい気がします。その辺の常識が知りたい……!

基本は 2 つ

ビームサーチと焼きなまし法! 後は考察やパラメータ調整で戦えそうです。

Psyho 氏

鉄則本

ヒューリスヒックの章を読みました。飛ばした章でしたが 鉄則本の中で一番良かった と思います。分かった感が凄い。複素解析みたく題材が良い説もあります。

複素解析の本が大体傑作に見えるのは、複素解析が傑作だから。

  • 局所探索法
    現在の解に小さな変更を加え、解が改善されるなら採用する。
  • 焼きなめし法
    局所探索法の解のスコアが悪化する場合も、特定の確率で採用する。悪化する場合の確率には exp (- \frac \Delta T) を使う場合が多い。温度 T を試行回数や経過時間に応じて変化させる。局所探索法は焼きなまし法の特殊な場合と言える。 Haskell で言えば until ループで表現できる。巡回セールスマン問題など。
  • 貪欲法
    次の一手のスコアが最善のものを選ぶ。スコア計算の指標は任意に定める。スコアの計算式が結果に大きく影響する。
  • ビームサーチ
    それぞれの候補の次の一手を考えて、高スコアの上位 K 本の候補を採用することを繰り返す。貪欲法はビーム幅 1 のビームサーチと言える。 Haskell で言えば foldl による入力処理で表現できる。

何が『探索』なのか?

より良い解を探していくことが探索で、解の候補も探索と呼んで良いのではと捉えています。 Thunder 本は未読のため不明です。

言語の選択

Common Lisp に挑戦するつもりでしたが、実行コマンドがコンパイル実行ではないのが気になっています。 Haskell で時間や乱数を使ってみるのも良いかも知れません。

toyboot4etoyboot4e

TODO: ゲーム

まずは未消化の鉄則本 A 問題をあたります:

  • 鉄則 A 32
  • 鉄則 A 33 (Nim)
  • 鉄則 A 34
  • 鉄則 A 35 (Grundy 数?)

未消化の水 diff も見たいです。


メモ

水 diff streak → PAST 上級本 → 典型 90 → EDPC → TDPC → ABC 過去問

流石に水 diff 100 問も解いた頃には十分でしょう……!

toyboot4etoyboot4e

range が相当遅い気がする

3 重ループの書き方で実行時間が 5 倍弱変化しました。

  1. forM_ 1 つと filter, range で書いた場合: 2,003 ms, 437 MB
  2. 1 次元の forM_ から range 風に手動で 3 次元添字を復元した場合: 923 ms
  3. forM_ 1 つとリスト内包表記で書いた場合: 749 ms, 436MB
  4. forM_ 3 つで書いた場合: 438 ms, 217 MB

感想

リスト内包表記が一番無難です。

range の場合 boxing が起こっている気がします。メモリ使用量を見てもたぶんそう。リスト内包表記は中間的な結果で少し謎ですが、 range よりはマシなので使っていけそうです。

雑念

range ぐらい綺麗なコードを高速実行したいです。 stream とか vector 関係に置換すればあるいは……? 思いつかなければ質問します。

toyboot4etoyboot4e

目の端に解答が 5 秒移ると、やがて『自力で』解けてしまう

解説の冒頭だけを読んで撤退 → 再考察 → なぜか解ける → 目に端に映った解説を無意識ちゃんが囁いていた → 誰もお前を愛さない

数学の問題を解いていた時もこの経験がありました。自分の『ユニークな』発想と全く同じものが解答に載っていて、実は解説を元ネタにしていたという。。

答えを見ると自力感が無いですが、しばらく問題を寝かさえておくと、自然とヒントが集まるかもしれません。そうした経験は役立ちそうです。

toyboot4etoyboot4e

関数合成

定義域に応じたサイズの配列を作れば良い。なるほど。

置換の合成

置換は単なる配列で表されるため、配列の出力に配列を繋いで合成します。

Bit 演算の合成

まんまこれですが
https://atcoder.jp/contests/abc261/tasks/abc261_e

  • 公式解説
    Int 値の関数を分解し、 i 番目の bit を取って i 番目の入力に移す関数が 64 本と捉えます。また個々の関数は 0 または 1 を取って 0 または 1 を返すため、長さ 2 の配列 (タプル) で表現できます。

  • ユーザ解説
    0b000...0 と 0b111...1 に分けて関数を保持

toyboot4etoyboot4e

入緑しました

よっしゃ!

主な統計

  • 取り組み期間: 11 ヶ月
  • 総 AC 数: 397
  • E 問題 AC 数: 37
  • 水 diff AC 数: 32

AtCoder Problems

このスクラップは2023/05/01にクローズされました