競プロ盆栽 (進捗表) (→ 典型盆栽の日報に移動)
このスクラップは
競プロ盆栽 (Haskell 盆栽) から進捗表やチートシートを分離したものです。
ノート
欲しいもの
- 主なアルゴリズム (鉄則本)
-
scc
,topSort
などData.Graph
の替わりの関数 -
Ix
キーや(Bool, Int)
キーが使えるIntMap
,IntSet
-
Ix
キーが使えるVector
- Algorithm Design with Haskell の動的計画法の章の情報
-
fgl
の利用経験 - 妥協の無い手続き型プログラミング (特に early return 周り)
捨てた手
- リストへのランダムアクセス
- 遅延評価で動的計画法 (テーブルサイズが大きいと、 unboxed なコンテナを使った時点で MLE)
- flip fix を使った再帰関数でメモ化 DP (DP 配列の累積和で撃沈)
(!! n) $ iterate step s0
典型的な罠
- 精度不足
-
Double
を使う - 可能なら整数で計算する (
を等式変形で消す、 2 分探索で平方根を求める)log
-
- 桁溢れ
-
Integer
を使えば安心 - 定期的に
mod
を取っても安心
-
- メモリ使用量がデカい
- 座標圧縮するか
IntMap
を使います
- 座標圧縮するか
主な定石
入出力
- 空白区切りの
unwords
, byte string builder の<>
,mconcat
,forM_ xs print
- Text.printf
replicateM nInput $ do ..
リスト
-
sortBy (comparing f)
,group
, HT.groupBy
Vector, Array
- Vector のソート:
VU.modify VAI.sort vec
- 2 次元の不変配列:
V.Vector (VU.Vector a)
として保存する、accumArray
で配列を作る - 2 次元の可変配列 (row-major): Mutable array を利用 (サイズ
(h, w)
, 添字は(y, x)
つまり(row, column)
).thaw $ accumArray ..
という手もある (unsafeThaw
) 。 - 2 次元リストの縦方向
scan
:VU.generate
で添字経由のアクセス
accumArray
-
accumArray
やData.Graph.buildG
でグラフ生成 -
IM.fromListWith
で sparse なaccumArray
からの scan で累積和
入力処理
その他
- Bit で状態を表す (→ 2 つで状態遷移を表す)
-
foldl'
で n 進数変換
典型的な解法
- 計算量を減らしつつ総当たり
- 貪欲法 + 2 分探索 (答えで 2 分探索)
-
mapAccumL
で入力処理 -
foldl'
で動的計画法 -
IntSet
で BFS / DFS
アルゴリズム
2 分探索 (ok/ng の境界を求める方式 (めぐる式))
参考記事: AtCoder灰・茶・緑色の方必見!二分探索を絶対にバグらせないで書く方法│FORCIA CUBE│フォルシア株式会社
-- {{{ Binary search
-- | Binary search for sorted items in an inclusive range (from left to right only)
-- |
-- | It returns an `(ok, ng)` index pair at the boundary.
-- |
-- | # Example
-- |
-- | With an OK predicate `(<= 5)`, list `[0..9]` can be seen as:
-- |
-- | > [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
-- | > <--------------> <-------->
-- | > ok ng
-- |
-- | In this case `bsearch` returns the `(ok, ng)` = `(5, 6)` pair:
-- |
-- | > > let xs = [0..9] in do
-- | > > print $ bsearch (0, 9) (\i -> xs !! i <= 5)
-- | > (5, 6)
bsearch :: (Int, Int) -> (Int -> Bool) -> (Maybe Int, Maybe Int)
bsearch (!low, !high) !isOk = both wrap (inner (low - 1, high + 1))
where
inner :: (Int, Int) -> (Int, Int)
inner (!ok, !ng)
| abs (ok - ng) == 1 = (ok, ng)
| isOk m = inner (m, ng)
| otherwise = inner (ok, m)
where
m = (ok + ng) `div` 2
wrap :: Int -> Maybe Int
wrap !x
| inRange (low, high) x = Just x
| otherwise = Nothing
-- }}}
尺取り法 (※ 重い)
-- | Returns inclusive ranges that satisfy the given `check`.
-- TODO: cheaper implementation
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)
転倒数
-- {{{ Inveresion number (segment tree)
-- | Calculates the inversion number.
-- |
-- | REMARK: The implementaiton assumes that the input is in range `[0, length - 1]`.
invNumVec :: (VG.Vector v Int) => v Int -> Int
invNumVec xs = runST $ do
let !n = VG.length xs
!stree <- newSTree (+) n (0 :: Int)
-- NOTE: foldM is better for performance
!ss <- VG.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 $ VG.sum ss
-- }}}
辞書順
-- | Returns 1-based dictionary order for the given array.
-- | WARNING: Use 0-based indices for the input.
dictOrderModuloVec :: (VG.Vector v Int) => v Int -> Int -> Int
dictOrderModuloVec xs modulus = runST $ do
!stree <- newSTree (+) (VG.length xs + 1) (0 :: Int)
-- Pre-calculate factorial numbers:
let !facts = factMods (VG.length xs) modulus
-- The calculation is very similar to that of inversion number. For example,
-- ```
-- 2 0 4 3 1
-- | | | | |
-- | | | | +-- 0 * 0!
-- | | | +-- 1 * 1!
-- | | +-- 2 * 2!
-- | +-- 0 * 3 !
-- +-- 2 * 4!
-- ```
-- So each expression is given as `(the number of unused numbers smaller than this) * factMod`.
!counts <- flip VG.imapM xs $ \i x -> do
!nUsed <- querySTree stree (0, x)
let !nUnused = x - nUsed
let !factMod = facts VG.! (VG.length xs - (i + 1))
let !inc = nUnused * factMod `rem` modulus
-- mark it as used
insertSTree stree x 1
return inc
return $ succ $ VG.foldl1' (\ !acc x -> (acc + x) `rem` modulus) counts
prevPermutationVec
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
Union-Find 木 (`MVector`)
-- {{{ Union-Find tree
-- | Dense, mutable union-find tree (originally by `@pel`)
newtype MUnionFind s = MUnionFind (VUM.MVector s MUFNode)
type IOUnionFind = MUnionFind RealWorld
type STUnionFind s = MUnionFind s
-- | `MUFChild parent | MUFRoot size`.
data MUFNode = MUFChild {-# UNPACK #-} !Int | MUFRoot {-# UNPACK #-} !Int
derivingUnbox
"MUFNode"
[t|MUFNode -> (Bool, Int)|]
[|\case (MUFChild !x) -> (True, x); (MUFRoot !x) -> (False, x)|]
[|\case (True, !x) -> MUFChild x; (False, !x) -> MUFRoot x|]
-- | Creates a new Union-Find tree of the given size.
{-# INLINE newMUF #-}
newMUF :: (PrimMonad m) => Int -> m (MUnionFind (PrimState m))
newMUF !n = MUnionFind <$> VUM.replicate n (MUFRoot 1)
-- | Returns the root node index.
{-# INLINE rootMUF #-}
rootMUF :: (PrimMonad m) => MUnionFind (PrimState m) -> Int -> m Int
rootMUF uf@(MUnionFind !vec) i = do
!node <- VUM.read vec i
case node of
MUFRoot _ -> return i
MUFChild p -> do
!r <- rootMUF uf p
-- NOTE(perf): path compression (move the queried node to just under the root, recursivelly)
VUM.write vec i (MUFChild r)
return r
-- | Checks if the two nodes are under the same root.
{-# INLINE sameMUF #-}
sameMUF :: (PrimMonad m) => MUnionFind (PrimState m) -> Int -> Int -> m Bool
sameMUF !uf !x !y = liftM2 (==) (rootMUF uf x) (rootMUF uf y)
-- | Just an internal helper.
_unwrapMUFRoot :: MUFNode -> Int
_unwrapMUFRoot (MUFRoot !s) = s
_unwrapMUFRoot (MUFChild !_) = undefined
-- | Unites two nodes.
{-# INLINE uniteMUF #-}
uniteMUF :: (PrimMonad m) => MUnionFind (PrimState m) -> Int -> Int -> m ()
uniteMUF uf@(MUnionFind !vec) !x !y = do
!px <- rootMUF uf x
!py <- rootMUF uf y
when (px /= py) $! do
!sx <- _unwrapMUFRoot <$!> VUM.read vec px
!sy <- _unwrapMUFRoot <$!> VUM.read vec py
-- NOTE(perf): union by rank (choose smaller one for root)
let (!par, !chld) = if sx < sy then (px, py) else (py, px)
VUM.write vec chld (MUFChild par)
VUM.write vec par (MUFRoot (sx + sy))
-- | Returns the size of the root node, starting with `1`.
{-# INLINE sizeMUF #-}
sizeMUF :: (PrimMonad m) => MUnionFind (PrimState m) -> Int -> m Int
sizeMUF uf@(MUnionFind !vec) !x = do
!px <- rootMUF uf x
_unwrapMUFRoot <$!> VUM.read vec px
-- }}}
Union-find 木 (`IntMap`)
セグメント木
参考: 競技プログラミングの鉄則
-- {{{ Bits
-- | Log base of two or bit floor.
-- | <https://hackage.haskell.org/package/base-4.17.0.0/docs/Data-Bits.html#v:countLeadingZeros>
log2 :: (FiniteBits b) => b -> Int
log2 x = finiteBitSize x - 1 - countLeadingZeros x
-- | Ceiling of log base 2 of an `Int`.
-- |
-- | # Example
-- |
-- | ```hs
-- | > log2 3
-- | 1
-- | > log2CeilInt 3
-- | 2
-- | ```
log2CeilInt :: Int -> Int
log2CeilInt x = msb + ceiling
where
msb = log2 x
ceiling = if (clearBit x msb) > 0 then 1 else 0
-- | Calculates the smallest integral power of two that is not smaller than `x`.
-- |
-- | # Example
-- |
-- | ```hs
-- | > bitCeil 3
-- | 4
-- | ```
bitCeil :: Int -> Int
bitCeil = bit . log2CeilInt
-- }}}
-- {{{ Segment tree
-- | A mutable segment tree backed by a complete binary tree.
-- |
-- | # Overview
-- |
-- | A segment tree is a cache of a folding function.
-- | Each node corresponds to a folding range and the node contains the folding result.
-- |
-- | A segment tree has a constant size and never be resized.
-- |
-- | # Operations
-- |
-- | Modification takes $O(log N)$, so creation takes $N(log N)$.
-- | Lookup takes $O(log N)$.
-- |
-- | # (Internal) Indices
-- |
-- | The complete binary tree has `2 ^ depth - 1` elements.
-- |
-- | - Child elements of a parent node `i` has index `2 * i + 1` and `2 * i + 2`.
-- | - The leaf indices start with `length / 2 - 1`.
-- |
-- | Example:
-- |
-- | ```
-- | 0
-- | 1 2
-- | 3 4 5 6
-- | 07 08 09 10 11 12 13 14
-- | ```
data MSegmentTree s a = MSegmentTree (a -> a -> a) (VUM.MVector s a)
-- TODO: Can I UNPACK? the funciton?
-- TODO: Generic queries and immutable segment tree (with `Show` instance)
-- | Creates a new segment tree for `n` leaves.
-- | REMARK: Always give a zero value. It fills all the nodes including parent nodes, and the parent
-- | nodes are not updated.
{-# INLINE newSTree #-}
newSTree :: (VUM.Unbox a, PrimMonad m) => (a -> a -> a) -> Int -> a -> m (MSegmentTree (PrimState m) a)
newSTree !f !n !value = MSegmentTree f <$!> VUM.replicate n' value
where
!n' = shiftL (bitCeil n) 1
-- | Updates an `MSegmentTree` leaf value and their parents up to top root.
{-# INLINE insertSTree #-}
insertSTree :: (VU.Unbox a, PrimMonad m) => MSegmentTree (PrimState m) a -> Int -> a -> m ()
insertSTree tree@(MSegmentTree !_ !vec) !i !value = _updateElement tree i' value
where
-- length == 2 * (the number of the leaves)
!offset = VUM.length vec `div` 2 - 1
-- leaf index
!i' = i + offset
-- | Updates an `MSegmentTree` leaf value and their parents up to top root.
{-# INLINE modifySTree #-}
modifySTree :: (VU.Unbox a, PrimMonad m) => MSegmentTree (PrimState m) a -> (a -> a) -> Int -> m ()
modifySTree tree@(MSegmentTree !_ !vec) !f !i = do
!v <- f <$> VUM.read vec i'
_updateElement tree i' v
where
-- length == 2 * (the number of the leaves)
!offset = VUM.length vec `div` 2 - 1
-- leaf index
!i' = i + offset
-- | (Internal) Updates an `MSegmentTree` element (node or leaf) value and their parents up to top root.
{-# INLINE _updateElement #-}
_updateElement :: (VU.Unbox a, PrimMonad m) => MSegmentTree (PrimState m) a -> Int -> a -> m ()
_updateElement (MSegmentTree !_ !vec) 0 !value = do
VUM.write vec 0 value
_updateElement tree@(MSegmentTree !_ !vec) !i !value = do
VUM.write vec i value
_updateParent tree ((i - 1) `div` 2)
-- | (Internal) Recursivelly updates the parent nodes.
{-# INLINE _updateParent #-}
_updateParent :: (VU.Unbox a, PrimMonad m) => MSegmentTree (PrimState m) a -> Int -> m ()
_updateParent _ (-1) = pure () -- REMARK: (-1) `div` 2 == -1
_updateParent tree@(MSegmentTree !f !vec) !iParent = do
!c1 <- VUM.read vec (iParent * 2 + 1)
!c2 <- VUM.read vec (iParent * 2 + 2)
_updateElement tree iParent (f c1 c2)
-- | Retrieves the folding result over the inclusive range `[l, r]` from `MSegmentTree`.
{-# INLINE querySTree #-}
querySTree :: forall a m. (VU.Unbox a, PrimMonad m) => MSegmentTree (PrimState m) a -> (Int, Int) -> m a
querySTree (MSegmentTree !f !vec) (!lo, !hi) = fromJust <$!> inner 0 (0, initialHi)
where
!initialHi = VUM.length vec `div` 2 - 1
inner :: Int -> (Int, Int) -> m (Maybe a)
inner !i (!l, !h)
| lo <= l && h <= hi = Just <$> VUM.read vec i
| h < lo || hi < l = pure Nothing
| otherwise = do
let !d = (h - l) `div` 2
!ansL <- inner (2 * i + 1) (l, l + d)
!ansH <- inner (2 * i + 2) (l + d + 1, h)
pure . Just $ case (ansL, ansH) of
(Just !a, Just !b) -> f a b
(Just !a, _) -> a
(_, Just !b) -> b
(_, _) -> error $ "query error (segment tree): " ++ show (i, (l, h), (lo, hi))
-- }}}
その他 dijkstra, topSort, scc, LCA, 2部グラフ判定・2部グラフ色分け、最大流、 IntMod, RollingHash など
鉄則本 基礎問題 (a 問題) の進捗
競技プログラミングの鉄則 演習問題集 をやっていきます。
この本のメインは問題集の方ですね。 典型 90 問 が応用力を問うのに対し、鉄則の 151 問はアルゴリズムのカバー率を重視したとか。盆栽に最適です。
問題の種類
基礎・応用・総合に分かれています。今回は基礎問題をやってテンプレートを蓄積するつもりです:
- a 問題 (75 問): 基礎問題
- b 問題 (56 問): 応用問題 (a 問題に対応する)
- c 問題 (20 問): 総合問題 (巻末の腕試し問題)
1. 全探索 [DONE]
算数で答えが出ない問題は全探索します。メタ読みすると、そもそも算数的に解ける問題は非常に少ないはずですから、まずは全探索を考えるべきです。可能なら 2 分探索やメモ化再帰で高速化します。
-
a01 ~ a05
初心に戻ってテンプレート無しでやってみました。ローカルのrunghc
だと実行時間が 1 秒を超える場合も、提出先の atcoder は 100ms くらいになります。コンパイルできる環境を用意した方が良いかもしれない……
ちなみにormul
(HLS のデフォルトのフォーマッタ) はリスト内包表記の中の改行を容赦なく削りますが、パイプの前に改行を入れるとそうでもないですね。
追記: 2 分探索 + 全探索、尺取り法 + 全探索、 Dijkstra + 全探索など、 brute-force 全探索は〜水 diff で頻出でした。全探索が基本だ!
2. 累積和 [DONE]
数列 → 累積和、ついでに累積和 → 数列と ismo 法を押さえます。 1-based index にした方が番兵を利用できて便利ですね。
2. 累積和
-
a06 (AC)
累積和をリストに入れると TLE (time limit exceed) になりました。今は懐かしき Haskell の壁……! Haskell は Tier 3 の言語と言われており 、いや言語を問わないかもしれませんが、オーダーを間違えると終わります。
Vector
のテンプレートを入れました。Vector.Unboxed.Vector
の中にはリストやVector
を持てない (持てるけれど型クラスが実装されない?) 点も要注意。
累積和はscanl1
で作れば 0-based index でアクセスできますが、scanl
で作ると 0 番目の要素が 0 になるのが (番兵的に) 便利です。また標準入力は 1-based index で与えられることが多く、scanl
を使った方が素直になります。 -
a07 (AC)
それぞれの入力が範囲のデータに影響を与える問題です。 In / out の 2 イベントに分けてからスキャンすることで処理します (いわゆる『いすも法?』) 。問題はこのスキャン用データを Haskell で作るのは難しい点です。 早くも自力で解けない問題がやって来ました 。
誰かの回答 を写経して解答しました (大感謝!) 。やはり 自力で Haskell を書くことはできない と認め、書き写す所から始めるべきです。
2 次元の mutable vector を作ってからscanl
を実行しています。『Haskellで戦う競技プログラミング』に mutable vector の使い方が載っているので参考にします。本来はaccumArray
で配列を作ってからscanl
と行きたいところでしたが、 array にはscanl
がありません。自分で作るか、 1 度リストに変換してからscanl
を適用することになると思います。 -
a08 (AC)
2 次元の累積和です。 Mutable array の使い方を理解しました。生成時のサイズは(h, w)
のように指定し、添字アクセスは(y, x)
の形 ((row, column)
の形) とします。この配列生成は冗長なコードなのですが、入力を読み取るIO
関数をmain
から分離すると、比較的綺麗になりました。
速く巧妙な解答 を見ました。 2 次元リストを操作して行列の array を生成しています。 Row-major な行列を表す 2 次元リストに対し、列方向のスキャンをするにはscanl (zipWith f) init matrix
の形が利用できると理解しました。 -
a09 (AC)
同様に 2 次元の累積和です。accumArray
で作った Array をthaw
で MArray に変えて、手続き的にスキャンしました。
やはり命令型言語 Haskell はつらい。宣言的に書けるようテンプレートを作ってもいいかも…… -
a10 (AC)
各章末のチャレンジ問題です。非常に簡単な問題だったのですが……解けませんでした。そりゃ茶色も遠いよなぁと思います。
3. 2 分探索 [DONE]
2 分探索ができれば茶色は堅い! 座標圧縮などで利用できる他、貪欲解と組み合わせての 2 分探索も典型です。
3. 2 分探索
-
a11 (AC)
OK / NG の 2 分探索理解した……! IntMap (内部的には木) に入れて検索 という別解もあるようです。 -
a13 (AC)
うかつな linear search が TLE を呼びます。 C 問題でも経験あります。 長大な配列を想像できるかが鍵 だと思いました。尺取法も想像次第で思いつける気がします。
尺取法では map しながら状態を持ち運ぶのは難しく、仕方がなくscanl
でタプルを作りました。 Rust の scan が欲しいのですが。 → リストならmapAccumL がありました。Vector
には無い関数なのでmapM
やState
の使い方を覚えるべき……? -
a14 (AC1 (
))O(N^3 log N)
半分全列挙というか分割統治法 (?) が正規の解答のようですが、ただの 2 分探索でも通ってしまいました。 -
a15 (AC)
ぐちゃぐちゃですが普通に通ってしまいました……。鉄則本 A 問題は計算量の制限が甘くしてあるかもしれません?
ByteString
で 標準出力を作る作法が身につきません。 Haskell の ドリルとかチートシートがあると良いなと思いました 。目次を見るだけでも、知識の種類を分類できて良いはずです。さらに内容豊富なら課金して手に入れたいところですが、そうそう都合良いものも無く……
あの方の回答がやはり良い ですね。 Set とか Map を使うとVector
よりもスマートな回答に見えます。吸収していこう。
4. 動的計画法 [DONE]
緑を狙うなら DP は必須! 考察が難しく、相当数の問題を解くことが求められます。
うかつにリストを使うと TLE になるため、 Vector
を状態に持った fold
が無難です。複雑な DP には可変配列を使ってしまいます。
4. 動的計画法
-
16
- 遅延評価で動的計画法
-
17
- 経路を残しながら遅延評価で動的計画法
- 答えを元に経路を復元
-
18
集合から一部を取って、その和が条件を満たすか。いわゆる『部分和』問題。部分和を全探索すると になって死亡。動的計画法なのは分かるが、ナップサック問題のように最適解を選べば良いわけでもなく、どのように重複するルートを削除できるのか……?O(N!) - すべてのルートを保持できる表を作って遅延評価で動的計画法
-
19
ナップサック問題! 無効な値の処理で困りました。遅延評価で解いたものを提出すると MLE ! まさか遅延評価では通用しないのか……? 『Haskell で戦う競技プログラミング』でも、array
を使って (遅延評価で) ナップサック問題を解くと、『サンクが大量発生』するとありました。全問解き直した方がいいかもしれない……-
fold
して AC 。それぞれのstep
で状態列を次の状態列へ移します。実はテーブル全体を持つ必要はありませんでした。 -
nobsun の解答 及び Youtube を見ました。実は
fold
の解答と似ていますが、状態を sparse にリストで持っています。またマージ処理 (><
) では 2 つのソートされた配列からダルマ落としのようにデータを取っていくのが面白かったです。巧い……!
-
-
20
- 最長の共通部分列 (連続性を問わない) を求める問題。
- 2 つの入力を 1 つずつ食っていく。難しい……
-
21
- 左側もしくは右側からデータを食っていき、最大のスコアを目指す
- いわゆる『区間 DP』らしいけれども、考察できませんでした。鉄則本だけでなく Educational DP Contest もやらねばと覚悟します。
- この程度の複雑さには即興で対処できる必要があると思います。また人と考えを共有する必要があるなら、 printf デバッグ (
trace
) を使って実際の動作を確かめるのは合理的です。 - 日頃から人への説明を意識して解答すると、競技プログラミングが 2 度お得だなと思います。
-
22
- ★ 4 だけどやたら簡単だった……。あまり DP という感じがしません。
- とか言って重大な考察ミス! しかも本書解説でその考察部分が書かれていないのが良いですね。
-
23
- マージ (重複除去) が難しい
fold
型の動的計画法として解きました。★ 5 ってほどかな……?
- マージ (重複除去) が難しい
-
24
- 考察で死亡。
IntMap
では (K, V) のうち K に対する 2 分探索しかできないため、実装も非常に難しい。切に nobsan の解説が欲しい……! -
MVector
で AC - 人の解答を読む
- 考察で死亡。
-
25
- 妙に簡単でした……?
5. 数学的問題
AtCoder ではよく数学的な問題が出てきます。素数の列挙、素因数分解や逆元の mod が解答には必須です。ゲームは苦手……
5. 数学的問題
-
a26
- エナトストレスの篩の高速実装ができなくてコピペしました。
- 無限リストへのフィルタが重かったみたいです。フィルタを
IntMap
にする解をコピペしていました。
-
a27
-
gcd
を呼ぶだけ!
-
-
a28
-
fold
しました。read
でString -> Int
ができます。
-
-
a29
- n 乗計算のキャッシュを持つやつ!
- 繰り返し 2 乗法と言うらしいですね
- 置換の合成も繰り返し 2 乗法!
-
a30
- フェルマーの小定理 (たとえば
, 証明は知らぬ……) を使って逆数の素数 modを計算する! ただし 万能ではない 。4 ^ 3 \equiv 1 \ \mathrm{mod} \ 3
- フェルマーの小定理 (たとえば
-
a31
- 血迷って WA 。考えていることとコードが一致するとは限りません。
-
a32
- 石取りゲームの必勝・必敗を求めよ!
- 数学的な問題を数学的に解こうとしないこと。それが競技プログラミングの秘訣だ! (1 敗)
- DP として解けるらしいですが……
6. 考察テクニック [DONE]
有る意味最高難度の章です。水 diff 後半〜青はこうした考察重視の問題が多い気がしています。
6. 考察テクニック
-
a36
- 珍しく算数で解ける問題!
-
a37
- 珍しく算数で (略
-
a38
- ん? どういう問題……?
-
a39
- 蟻本で心が折れた問題でした。貪欲解で AC 。
- ヒューリスティックではなく考察の章に入っており、最適解が求まるためかと
- 考察の方法としては、取りうる戦略を総当たりして最善のものを選ぶ?
- DP としても解けるみたい……?
-
a40
- 入力をもとにカウントを作成
-
☆ a41
- 最後の一手を考えて…… (敗北)
- このような区間操作の問題は典型である感じがします。
-
☆ a42
- まずグラフを書いてみる (敗北)
- 区間ごとにカウントしてみる
-
a43
- 蟻本 1 問目
- 意味のない制約は典型
-
a44
- 反転状態を保持、反転 view を経由して配列にアクセスする
- PAST にも出てました。
-
a45
- マジック……
7. ヒューリスティック
なんとなく『ヒューリスティックができれば社会で通用する』イメージがあります。なおヒューリスティック系コンテストとしては、AHC が月 1 で開催されています。
- A49: ビームサーチは貪欲法……もとい貪欲砲だと理解しました。手持ちの探索を発展させて、最善手 k 個を抽出することを繰り返します。
8. データ構造とクエリ処理 [DONE]
主に containers
を把握します。キュー、ヒープ、 Set, Map の使い方を学んだ後は、ローリングハッシュやセグメント木を実装します。
8. データ構造とクエリ処理
-
a51
- リストをキューとして使う、
- 真面目にパースするのはやや難しそう……
-
a52
- キューが欲しい。まず
heaps
の利用を考えました -
containers
パッケージの Data.Sequence がキューのようです。内部的にはIntMap
なのかな - 基本的にパタンマッチを使わなければ分解できません (
headL
やheadR
は無い) -
<|
で左端へ、|>
で右端に要素を追加できます -
:<|
で左端、:|>
で右端の要素にパタンマッチできます - いつか 本物のプログラマはHaskellを使う を読むべきですね
- キューが欲しい。まず
-
a53
- heaps で AC, Dijkstra 実装へ (A64)
-
a54
-
Map
またはHashMap
を String キーのマップとして利用できます。 - 順序付けは不要のため
HashMap
を使ってみました。
-
-
a55
- Set なら String キーを利用でき、かつ順序付けられています
- String キーである必要は無かったか……
- なぜか Set では WA, IntSet では AC 。ローカルでは両方 AC なのですが……?
-
a56
- Hash 値というのは、 seed 値を通過した経験のようなものですね
- B 進数による hash 値は、以下の漸化式を一般化すると fold 計算できます:
h(1, 2) = B^{2} s_{0} + B^{1} s_{1} + B^{0} s_{2} h(1, 3) = B h(0, 2) + B^{0} S_{3}
- またスライスのハッシュ値は
のように差をとって計算できます。累積和と同様ですね。 (累積和と同様に高速になるよう B 進法を使うのですね) 。h(3, 4) = h(1, 4) - B^2 h(01 2) -
が非常に大きくなる/オーバーフロー前に mod 計算が必要なため (?) 事前計算しておきます。徹頭徹尾解説に従いました。こうした細かい部分も、自力で思いつく気はしません。B^n
-
a57
- 何度も置換すると同じ位置に戻ってきます。しかし何回置換すれば元の位置に戻ってくるかは位置によって異なります。このやり方だとややこしい
- 置換は合成できる。た、確かに……!
- 有効 bit を fold して任意の回数の置換を計算します。
- 単品なら比較的簡単ですが、タブリング前提で問題を組まれるとバグで苦しむ気がします。
-
a58, a59 (AC)
- セグメント木を実装しました。 Union-Find の実装よりもちょっと難しい。
9. グラフアルゴリズム [DONE]
緑を狙うにはグラフは必須! グラフの問題はいくらでも難しくできるはずなので、相当数の問題を解かなければ実戦では得点になりません。難しい。。
9. グラフアルゴリズム
-
a61, a62
accumArray
が使えるかという問題です。ByteString.Builder
(Builder
) にも慣れてきました。 -
a63 (AC)
DFS だと TLE で、 BFS だと空間のサイズ程度まで計算量を削減できます。 DFS よりも状態が増えて難しい。また BFS を使っていても なぜか TLE になった のですが、IntMap
を使って早めに重複を除去すれば通るようになりました。むむむ。。。 -
- heaps で Dijkstra を実装!
- 意外と BFS よりも簡単でした
- なお到達不能なノードの扱いを忘れて WA ……
-
- 単なるメモ化 DP 。遅延評価でサンクを作っても十分間に合います。
-
a66 (AC)
Union-Find そのものです。 誰かの解答 のおかげでテンプレートを作ることができました。命令型言語 Haskell も手強い。 -
a67
最小全域木。貪欲に最低コストの辺を追加して AC. -
a68
最大流量。残余ネットワークを考えると増大道を探索できるので、繰り返し流し込み方を調整します。 -
a69
二部マッチング。始点と終点を追加すれば最大流問題に帰結 -
a70
いかにグラフの問題に帰結するか。あとは BFS
10. 総合問題
-
- Haskell では手続き的に解くのは難しい……。図を変形してしまっても構わんのだろうということで、ソート済み配列のマージ処理に帰結します。
- しかし最後のテストケースで WA 。 貪欲法ではダメらしい ですし、そもそも嘘解法だったのでしまった
-
A77
典型 90 問の方で AC 。良問です
感想
- 早解きにも練習が必要なことが分かりました。
- 難しかった問題はもう一度解きたいものです。
もう一度解きたい/類題を解きたい問題
- 答えで 2 分探索: 食塩水問題など
- 尺取法: PAST 過去問
- 考察テクニックの B 問題
- 動的計画法の B 問題
- 考察典型
- ローリングハッシュ、ダブリング、セグメント木: ABC や PAST 過去問
感想
本の内容に関して
1,000,000 点! ライブラリが潤いました。
ただテストケースが弱いため、尺取り法の替わりに 2 分探索しても通ったりして注意が必要です (PAST なら、尺取り法 (
本の使い方に関して
分からなければサクっと解説 AC し、ライブラリを揃えることに専念したら良いと思います。演習問題はいくらでもありますので、取り零しは心配ありません。
B 問題を解くべきかは検討中です。
本の難易度に関して
あーだこーだー によると鉄則本は初心者向けだったそうです。演習問題は相当難しいと感じますけれどね……!
まあ『答えで 2 分探索』とだけ聞いても食塩水問題は普通解けません。類題演習が必要です。鉄則本の効果は、解説 AC の前提条件というラインかもしれません。
鉄則本に無かった内容
以下の典型は、常設コンテストや過去問から学ぶ必要がありました。
- 座標圧縮 (座標圧縮は鉄則本にあったかもですが)
- トポロジカルソート、強連結成分、 2-SAT
- 二部グラフの判定、色分け
- 転倒数 (binary index tree あるいはセグメント木による実装)
- 辞書順 (binary index tree あるいはセグメント木による実装)
- LCA (最小共通祖先: ダブリングによる実装)
- DP (期待値 DP, 集合 (bit) DP, 桁 DP) (区間 DP は演習にある)
以降は僕も未履修です。俺達の盆栽はこれからだ!
- 最小費用流
- 2 次元の座標圧縮
- 拡張ユークリッドの互助法、包除原理、中国剰余定理
- 高度典型
- など
典型 90 問 (〜 ★ 3)
未だに鉄則本 a 問題をやっていますが、新規アルゴリズムの盆栽ばかりというのも (非常に好みですが) 疲れが出ます。息抜き程度に典型 90 問もやってみます。
これらの問題は比較的考察が主で、ある程度解けば緑色が狙える程度という認識です。
リンク
Qiita から難度表を転記しました。★ 3 ~ 4 が解ければ水色相当……?
星の数 | ★1 | ★2 | ★3 | ★4 | ★5 | ★6 | ★7 |
---|---|---|---|---|---|---|---|
配点 | 1 点 | 2 点 | 3 点 | 4 点 | 5 点 | 6 点 | 7 点 |
Diff. | ~149 | 150~399 | 400~799 | 800~1199 | 1200~1599 | 1600~1999 | 2000~ |
環境構築
典型 90 問を atcoder-cli
でダウンロードすると 001/
, 002
, .. というディレクトリができます。これらの 001/Main.hs
, 002/Main.hs
, .. に対し、 なぜか LSP でフォーマットできませんでした 。
そこで a001/
, a002/
のようにアルファベットから始まるディレクトリ名に変更すると、正常に動作するようになりました。謎……。
$ rename 's/^0/a0/g' *
$ # `contest.acc.json` も直しておきます
実装
-
01 ★4
- 考察で死亡
- 貪欲法により、目標とする区間の長さに対して 1 意な対応が決まる
- この対応 (『答え』)で 2 分探索して目標値と一致する場合を求める
-
Vector
へのmapAccumL
相当をState
モナドで計算し、累積和を数列に -
foldl' step x xs `step` lastX
で畳み込まれる要素を追加できる -
trace
で print デバッグ
- 考察で死亡
-
02 ★ 3
- DFS + 枝刈り
- 刈るべきケースは事前に分かるためメモなど不要
- リストの要素は左から追加する (
:
)
-
03 ★ 4
-
Sparse な Union-Find 木 + 各グループで DFS? 解けそうだけれど大変だなぁ……- 問題文をよく読むと、 1 繋がりなグラフが与えられると書いてある
- しかも N-1 本しか辺が無いため閉路が無いことが分かる (木である)
- 『 木の直径 』に 1 を足したものが答え
- DFS だけでも十分むずいよ (リストを返す形にすると良い)
-
-
04 ★ 2
-
VU.generate
に親しむ (各要素を添字から生成できる) 。いすも方にもVU.generate
を使ってしまって良いかも。
-
-
07 ★ 3
- ソートして 2 分探索
-
08 ★ 4
- 解答: DP で状態も表現する。最近の ABC もこのパターンだった
- Haskell で動的計画法を書くための3つの方針:
-
10 ★ 2
- 累積和。より発展的には、累積和の添字が疎な場合の対応を考えることになる気が
- 座標圧縮で疎な添字 → 密な添字 (Vector or IntMap?)
- 累積和。より発展的には、累積和の添字が疎な場合の対応を考えることになる気が
-
12 ★ 4
- Union-Find (エッジケースに気を付ける (1 点だけの場合))
-
14 ★ 3
- ソートしてから差を取る。どこかで類題を解いたことがなければかなり難しかった。
-
16 ★ 3
- 算数のように解こうとして死亡。難しめの C 問題が出てきたらまずいな……
- 解答: 効率的に全探索せよ
- なお貪欲で最善解を求めることはできなかったため、あくまで次元を減らした全探索の結果から最小の値を持ってくる必要があった。散々だ!
-
18 ★ 3
- 算数としては簡単だけれど、 3 次元ベクトルのテンプレートを持っておらずガタガタなコードに
-
Int
の一時変数をDouble
に変換するのも非常に汚い (警告を無視して積極的に shadowing するべきか)
-
20 ★ 3
- まさかの WA
- 解答: 誤差を無くせ (整数で処理せよ)
- というかオーバーフローしないのか…… (どのみち
Integer
ならオーバーフローしないし)
-
22 ★ 2
- 証明せずに提出しちゃった……
- GCD: greatest common divisor, lcm: least common measure (こういうのを教えてくれる先生たちだった)
- 20 問目の
といい、意外と高校数学の貯金があると言える状況なのかもlog
-
24 ★ 2
- こういう所でショートコーディングを学ぶと得るものがある (やっていない)
-
26 ★ 4
- 木を DFS で色分けして WA, 同じ論理のつもりで書き方を変えると AC 。デバッグができるかが重要
- 人の解答を読んで色分けのデータの持ち方を参考にする
-
27 ★ 2
-
foldM
で状態を持ちつつ副作用を起こす - unordered-containers の HashMap を使用しました
-
fold
で答えを集めて後からprint
するのも良いですね
-
-
28 ★ 4
- ismo 法。バグって大変でしたが、その分テンプレートが潤いました。
- ismo 法では可変配列 (ST モナド) を使っています。列方向の
scan
で書けたら一番良いとは思いますが……
-
32 ★ 32
- DP は辛い、グラフでもない
- N の小ささから全探索でも問題無いことに気付きます。意外と苦戦しました。
-
33 ★ 2
- 珍しく算数。楽勝……
- じゃなぁあああああああああぁぁい! まだ終わってない! (敗北)
-
38 ★ 3
- Early return せよという形で基礎的なアルゴリズムを手書きさせるのがある意味典型ですね
-
Integer
を使えば一瞬ですが、題意に沿って素因数分解から LCM を計算します - ところが素因数分解は題意に沿っていなかったのですね (TLE などで惨敗)
- LCM は GCM から計算する形だと (実装が) 楽
- 算数ができない悲しい事件だった……
-
44 ★ 3
- 手続型プログラミング。
IORef
とMVector
でやり過ごしました。 -
foldM
を使えばIORef
を消せますね。
- 手続型プログラミング。
-
46 ★ 3
- array を multiset として使います。後は総当たり
-
48 ★ 3
- 基礎的な動的計画法を組みました。図を書くと経路の見落としが減り、ロジックミスが減ります。
- しかし
を超えるサイズのテーブルで DP などすれば TLE は必至です (惨敗)10 > 9 - 貪欲で AC しました。
-
50 ★ 3
- 基礎的な動的計画法
-
52 ★ 3
- 珍しく算数。よく分からない問題だ……?
-
55 ★ 2
- 組み合わせの全列挙で間に合う……はずが TLE. テンプレートが遅かったようです。
- リスト内包表記で組み合わせを列挙する
-
map
やfilter
はリスト内包表記の中に埋め込んだほうが速い (遅くならない) -
foldl1'
をインライン展開するとさらに速い (えぇ……) - Haskell には難問でした
-
61 ★ 2
-
Data.Sequence
はindex
が遅い……はずが通りました。テストケースが弱いかもしれません。 -
MVector
ベースのキューを自分で実装した方が安心ですが、飛ばします。
-
-
67 ★ 2
- そういえば進数の変換が苦手だ?!
- n 進数表記は
[Int]
で持つことができますね。 digits から関数を借りてテンプレートに追加しました。 - 公式解答: 8 進数 → 10 進数 (
fold
), 10 進数 → 9 進数 (quatRem
再帰)
-
69 ★ 3
- 1 つずつ追加する/繰り返し二乗方法
-
76 ★ 3
- 2 分探索 or 尺取法
- 尺取法がサクっと書けると良いのだけれど
-
78 ★ 2
- 典型 90 問にしては、珍しく簡単な灰色だ
- 線形探索しても最大で
回しか探索しないため安心M - グラフを行列で持つと MLE するよ、という出題意図だったみたい
-
82 ★ 3
- 2 の逆元 (素数 mod) が必要だし、オーバーフローの対策も必要
- これは緑では……?
- 84 ★ 3
- どう解こう……
- 余事象で解けた
- 間違えて 2 回解いてしまいました。今度は尺取法。過去の自分頭いい……。そして解答に両方載っていますね
所感
- 灰色でも Haskell では難しい問題が多いです。
- 茶色の方が簡単な問題が多かったです。
〜緑 diff 50 問 [挫折]
そろそろ緑を狙いたいのです。 4 完させてくれ!
予定
休暇が降ってきたので 毎日 5, 6 問ずつ緑 diff の問題を解いていきます 。グラフ、 DP, mod 計算を抑えます。
追記: 相当厳しかったです。典型 90 問の〜 ★ 3 や鉄則本 A 問題を優先したいと思います。
12/27
かなり難しかったので、並行して Educational DP Contest (EDPC) も解いていくことにしました。厳しいや。。
12/28
ギリギリ緑な 5 問 (ABC): 4/5
-
ABC 271 D:
-
fold
で解ける簡単な動的計画法として AC 。解けるじゃん!
-
-
ABC 266 D:
- 同上。
traceShow
で境界値バグをデバッグします。解けるじゃん。
- 同上。
-
ABC 271 C:
- デバッグに時間がかかった上に WA 。うーむ。。
- テストケースをダウンロードしてようやく AC 。重複を考慮したり、境界値バグを直したり。
- 知識だけではなく、こういう頭の良さも問われるのですよね。 C 問題で高 diff な問題の方が、ロジックが難しいかもしれません。
-
ABC 272 C:
- DP というよりも探索として
fold
して WA 。まぁ TLE/MLE じゃないならいける……! - なんだ
print
部分を間違えただけじゃん。 緑下位は解けるなぁ - 他にも提出している人がいます。貴殿も精進中か
- DP というよりも探索として
-
ABC 277 D
- これが D 下位って……。 やっぱり緑下位は解けない
- 全体の和から取れる和の最大値を引く
- n <= m の場合はループを考慮しなくていい。再帰で頑張る
- n > m の場合は、連続列が 1 つだけの場合ならループを考慮する。
- 再帰の実装力が足りない
- 解答を見ると、探索で解けと書いてあります。探索の方が実装が楽だけれども、 Haskell でやるのは十分つらい……
まぁまぁ解けますね。
12/29
典型 90 から 3 問: 2/3
-
典型 34
- 出題ツイートの図を見なければ考察で脱落
- 実装も手続型になり非常に厳しい。境界値バグを直したつもりだが RE, しかも
の時点で TLEN^2 - 解答によると尺取法 (左から右へ窓を動かしていくイメージ)
- 困難は分割せよということで、 multiset の API を追加して AC 。更なる実装には IntMultiset が参考になりそうです。
- 境界値バグの print デバッグ (
traceShow
デバッグ) を頑張りました。
-
典型 42
- 考察すれば配る DP 。むずい〜〜
-
典型 43
- 変な BFS として解くのは実装が重い……
- 解答を見ると『拡張 BFS』とのことでした。全然ピンと来ないぞ……!
歯がたちません。 鉄則本をやり遂げたとしても緑は厳しい気がしてきました 。後日回答 AC していきます。
予定を変えて:
鉄則の BFS / DFS の B 問題: 2/2 [DONE]
DFS で TLE になるのはおかしい……。Haskell が低数倍の差で TLE になる言語なのか、僕の実装が極端に鈍なのか。誰かに課金して添削してもらいたいほどです。
12/30
鉄則 DP B 問題: 3/5
-
鉄則 B 19
- ナップサック問題では
fold
するのが安牌。学んでいたので AC 。 - リストを使う場合は枝刈りを真面目にしないと TLE になります。オーダ的には重複除去だけで良い気がしているのですが。
- ナップサック問題では
-
鉄則 B 20
- 考察で死亡、解答を聞いても半信半疑
- しばらく具体的な例を考えて納得。でもむずいよ……
- nobsun の解説はなお難しいと思うので Youtube は飛ばします。いつかやるから……!
- 番兵を使って分岐を減らしている 解答が上手い! Haskell には基本 early return が無いですからね。
-
鉄則 B 21
- 普通の DP. テーブルで全状態を表すためには、開区間
でセルを作るのがコツですね(l, r) - 境界値 (最小値) チェックで WA を経験しました。こういう所でパフォーマンスに差が出るのね……!
- 普通の DP. テーブルで全状態を表すためには、開区間
- 鉄則 B 23
- 鉄則 B 24
時間がなくて無の進捗が生み出されています。
リストを使った Haskell らしい格好いい解答に挑戦しないことが重要だと思いました。それは後でやろう。。
12/31
01/01
01/02
01/03
01/04
遅延の取り返し
緑 diff streak の 14 日間でリベンジできました。
Educational DP Contest (A ~ E)
-
EDPC A
- 遅延評価で通ります。これくらいなら心配ありません。
- 今この瞬間も、僕を含めて 8 人くらいが解答を提出しています。人口多いなぁ
-
EDPC B
- 遅延評価で通りました。制約的に不安でしたが……。
-
fold
(list + 重複除去) も大体解法を考えました。 -
MVector
でボトムアップに解く方法も行けます。
-
EDPC C
-
fold
で解けます。楽。
-
-
EDPC D: ナップサック問題 (V が大きい場合)
-
Vector
(w -> v) のfold
で AC -
リストでもなんとか AC
- TLE の原因が謎 で困ります。テストケース次第では TLE だよな……
- やはり Vector で fold が安牌、リストは謎
-
-
EDPC E: ナップサック問題 (W が大きい場合)
- リストで AC
-
Vector
(v -> w) のfold
ABC ノート
-
ABC 284: 3 完 (29 分) 494 (5255 / 9299)
- C は DFS で。バグっていて躓きました
- D が解けず。。 2 個目の素数は探索しなくて良かったか……
- E は DFS で解こうとして失敗
- F は明らかに B 進数ハッシュだと思いましたが、実装が間に合わず (でも解けそう)
- なんか全体的に解けそうだ……!
-
ABC 285: 4 完 (86 分) 860 (2380 / 8173)
- A で死亡、 B で死亡、 D で死亡
- A は算数、 B は全探索、 C も算数、 D はループを探すだけ
- E は普通に解けない。。
-
ABC 286: 4 完 (72 分) 883 (2469 / ?)
- A 問題が難しかったよ。。ブランクが空くとこうなります
-
slice l r = take (r - l + 1) $ drop l
を作れば良かったです
-
- B 問題をオートマタで解こうとしてパタンマッチで十分だった
- C 問題は右端に移動できる文字は左端の文字だけだと読むのに時間がかかりました
- D 問題は
fold
で解ける簡単な DP なので、これで点をもらうのは申し訳ない気もしました - E 問題はけんちょん本でみたやつ! これとか動的計画法の難しいやつはいずれ解けると思います
- A 問題が難しかったよ。。ブランクが空くとこうなります
-
ABC 287: 4 完 (72:02) 883 (2465 / ?)
- A:
For
の大文字に気をつけます - B:
IntSet
に入れて比較しました - C: DFS で解いたのですが大変時間がかかりました。グラフに弱いです
- D: 累積和。これでレートが稼げるのは不思議な感じですが……
- E: 地層を積み重ねていくような計算ですが……
終了後、[Int] -> [[Int]]
をaccumArray
で表現して AC 。これは精進していれば時間内に解けたかも - 現レート
667
です。最近は典型的な問題が多かったですが、そろそろ速解や数学が求められてレートが落ちる気がします。精進再開しよう!
- A:
-
ABC 288: 3 完 (89 分) 560 (4365 / 7645)
- A: map します
- B: 0 パディング的なことを考えて死亡 ソートにかけるだけで十分でした
- C:
Data.Graph
で連結成分を求めて、余分な辺の和は辺の数 - 頂点の数 - 1
の和- 別解: Union-Find で閉路を作る辺の数を数え上げ
- D: 考察で死亡
-
ABC 289: 4 完 (93:10) 735 (3586 / 7804)
- A: Lambda case (
\case -> ..
) が便利ですね - B: Union-Find で
group
するのが大変でした。どうすれば良かったのか……- やはり灰 diff でも相当難しい問題はある……
- C: Bit 全探索ですべての組み合わせを試します
- D: DP として解けなかったので探索として解きました。重複除去が命……!
- 解答: 1 ~ N までイテレートして
. なるほど!O(N * M)
- 解答: 1 ~ N までイテレートして
- E: 時間切れです
- A: Lambda case (
-
ABC 290: 3 完 (78:00) (4317 / 6180)
- A: 最速はもらった!
- B:
mapAccumL
そのもの! - C: 爆死しました。
groupBy
が思っていたのと違った……
ghci> groupBy (\a b -> a + 1 == b) [0, 1, 2, 3] [[0,1],[2,3]]
- D: 全然解けません。解説を見てなるほど……
-
ABC291: 4 完 (33:26) (2386 / 6849)
- A: 最速はもらった!
- B: 最速はもらった!
- C: 愚直に
foldl'
します。 - D: 愚直に
foldl'
します。余りを取るのは忘れずに…… - E: 木の最も長いパスを通れば良い。が、どうすれば……
- どうやって根を見つける? その後の探索は DFS では間に合わないのでは?
- トポロジカルソートというそうです?
- 何がソートなのかかまだ分かりませんが、 in_degree に注目します
- In-degree が最小 (0) の頂点を記録
- 最小の頂点が 2 つ現れたらエラー
- 0 の頂点が無いなら閉路がある
- グラフから頂点と取り除くとき、隣接する頂点の次数を更新する
- トポロジカルソートは
Data.Graph.topSort
で再現できるのでしょうか
-
ABC 292: 3 完 (44:25) (3581 / 7550)
- A: 最遅はもらった……!
Vector
2 本でマップします。ケース変更の関数があるのかな- ありました: Data.Char.toUpper, Data.Char.toLower
- B:
MVector
とforM
で。 - C: 素数を列挙して 1 ~ N の約数の数を……と思ったが失敗。。。 定数倍の最適化を考えるのは止めて、既存の関数を使って AC しよう と決めました。
- D:
Data.Graph.components
の使い方が分からなくて辛かったです。あのライブラリは一見便利そうなのが良くない。。topSort
以外使わないことに決めました 。自作 DFS で AC! 典型はいけるか! - E: トポロジカルソートの逆順で計算しようとして失敗。上手くやろうとせず、すべて数え上げてしまえば解けるのですね……
- 今回はチャンスでしたが逃しました。。
- この程度の強度なら毎日いけます。最近やっている緑 streak の方がキツイですね。典型的な実装は追いついてきたので、 1 問 6 時間みたいなシーズンが過ぎてくれると良いのですが。
- A: 最遅はもらった……!
-
ABC 293: 3 完 (36:13) (3752 / 7935)
- A: 基本に戻って再帰関数でマッチして処理
- B: 手続き型プログラミングが無難です
- C: 全探索
- D: DFS. なんでこれが WA なんだろう………
- 終了後 Union-Find に変更して AC. うーむ
- テストケースが公開されたら DFS で通せるようにする
- E: 等差数列の和なのですが、答えが 0 の場合等式変形で詰まってうーむ
-
ABC 294: 5 完 (47:03) (1383 / 6581)
- A: 最速はもらった!
- B: 最速はもらった!
- C: HT.mergeBy でやると簡単です
- 後から 2 分探索で番号を振るのも確かにアリですね
- D: クエリ処理 (茶 diff では……?)
- E: 再帰関数 (茶 diff では……?)
- F: 全探索しようにもメモリが足りません。むむ……
-
ABC 295: 3 完 (20:24) (? / 7737)
- A: 1 行目を捨て忘れてハマりました。
- B: 強引に AC.
- C: Mutliset 的な IntMap で。
- D: マージできなくて考察失敗。。連続部分列?
- ある種の累積和ですか……
-
ABC 296: 3 完 (40:25) (4680 / 7617)
- A: 最速はもらった!
- B: 少し混乱しました。
- C: 自前の
bsearch
は添字を返すんですよね (敗北) - D: 敗北。
で求まるため、片側の整数の探索の範囲を狭めるだけで良かったみたいです。商列挙はb = x / a が典型か……!O(M) - E: ゲームは苦手です。もとい、読解力
- F: あと一歩と思いきや全然……
- G: 典型らしい……?
-
ABC 297: 5 完 (104:49) (1533 / 6692)
- A: OCaml の人と同着です。
- B: 最速はもらった!
- C: 最速はもらった! (今回 Haskeller は少ないか)
- D: 灰 diff 的失敗でペナルティを負いました orz
- E: よく考えると普通に
fold
で解けました。ここまで茶 diff の早解き回か。-
IntSet
にdeleteMinFind
が入っているのは非常に良くできている気がして来ました。
-
- F: 解析的には標準偏差の積だと思うのですが、 DP 的解法を思いつかず。グリッドがループしていれば固定できるのだけれど……
- G: ゲームも解けるようにならないと
以降は Twitter で
鉄則本 C 問題
B を飛ばして C の簡単なところを解きます。
前半
灰色〜茶色前半だと思います。 A 問題を解いた後なら全然解けます。
-
C01: 単純計算
-
C02: ソート
-
C03:
scanl'
-
C04:
まで取って全約数をソート (敗北)\sqrt N -
C05: 2 進数表記からのマップ、 1-based index に注意
-
C06:
print
するだけ? -
C07: 累積和と 2 分探索
-
☆ C08: 全探索 (敗北)
-
C09: イテレートするタイプの単純な動的計画法。遅延評価 (
listArray
) よりはMVector
の方がマシですかね…… -
☆ C10: 単なる累乗の mod ですが考察はできませんでした。 → 全パターンで 2 列目が 7 通りなので全体を 7 倍で良い
後半
茶色後半〜という感じ。普通に解けなくなってきました。良問揃い!
-
☆ C11: 少数で二分探索 (敗北)
-
☆ C12: 切断の最小数を考えましたが、同じ線を 2 回切ってしまってうーむ。 N が小さいので全探索 (+ 累積和?) っぽいですね。
- 全然ダメやんな?(・ω・)
- なんとか解答 AC しましたが難しい……
-
☆ C13: 全部の組み合わせの積を取ると時間切れですし、、足し算だったら DP なのにな
- 割り算 (mod) でいけるのか……
-
group
で数を数える、0
は例外 - なんとか AC しましたが十分難しい (こういう数学的問題はたぶん茶 diff なのが辛い)
-
main
を綺麗にしたい。 なるほど0
でパタンマッチすればいい ですね
-
☆ C14: 単なる
dijkstra
? いや解けんぞ……- 同距離の経路が 2 つある場合に対応できません
- 全経路辿るのめんどいなぁ……
- 解答を見て納得しました。ぐおおお 自力で解けたかった
-
☆ C15: 絶対出席の会議の左と右で分けてそれぞれが区間スケジューリング問題。たぶん解けそう?
-
になるじゃんN^2 - 両方向からの累積和とかでなんとかしたいけれど……
- 解答: 前提を見てなるほど。後は DP か……?
- 解答: なんとか AC! 良問です。しかし WA. この問題だけテストケースが公開されていないので詰みました。
- サンプルが間違っているという指摘が GitHub に上がっています。
-
-
☆ C16: グラフ分からへん
- 解答を見ました。どの空港からスタートしても良いようです
- じっくり読んでなるほど……。これは難しい。。
- 実装も中々大変でした。
-
C17:
Seq
2 本で AC -
C18: うーむ DP かなぁ
- 区間 DP の一種らしいですが難しい。。
- EDCP N, TDPC I を解いて類題を補充しました。まだ十分に解けません。
-
C19: DP だと思うけど難しそう
-
C20: これがヒューリスティック問題 (現実的な時間で解けない問題) かぁ
Chokudai Speerun
典型コード片一覧という感じです。後半の問題は十分難しい……
S001 [Done]
-
G:
- logBase で桁数を数えようとして精度で爆死
- 普通に 10 で割って桁数を数えました。
-
H:
- 鉄則本 A024 。
になって解けない……O(N^2) - 部分問題で
の最大値を\{ dp_i \}_i (0 <= i < x) 程度で取得するには。O(log N) - 区間木で AC. やっぱりこれが一番簡単かな……!
- 鉄則本 A024 。
-
I:
- 累積和 + 尺取り法 or 2 分探索
- 2 分探索の方が簡単だと思います。
-
J:
- 転倒数の実装を持っているか。
-
K
- 辞書順は典型……!
- 転倒数の計算に似ています。
- 文字列 → 辞書順をやりましたが、辞書順 → 文字列 もやっておきたいところです。
-
☆ L:
- 座標圧縮 (添字圧縮) すると自前の転倒数計算が使える
- ソートに必要なスワップの回数はすべて偶奇が等しいということか
S002
- ☆ H: 式変形。
- I: 式変形で最強の食べ物の必要条件が分かります。
- ☆ J: これ解けないから緑まで行けないんだな〜〜〜〜〜〜〜〜〜
- ☆ K: Greedy 解が通らず。通用しません。
-
☆ L
- 手も足も出ないぞ……
- なぜか L x L グリッドの中に置く気がしていたのですが違いました
- 最長増加部分列、みたいなキーワードを見てしまいました。このヒントありで解答にたどり着ける?
TODO
緑 diff streak [完了]
ABC 255 ~ 現在までの緑 dif の問題を埋めます。睡眠時間は消えますが……
-
ABC 291 E (02/28)
- In-degree が 0 の頂点から削除します。辛すぎだよこの問題……
- もしくは、
Data.Graph.topSort
の結果に対し、条件から一意に定まるかをテストします- 閉路は無いか、直前の頂点と繋がっているか
- グラフの知識不足 ということですね
- トポロジカルソートの実装は自力で書ける気はしない……
-
鉄則 A68 (03/01)
- 最大流問題。 Haskell ではわりと難しいので、緑 diff 扱いして 1 日かけて取り組みます。
- とにかく大変だったしコードも酷い、直せる気もしない。。
- 後日剪定します。 fgl の MaxFlow はどうなっているかな
-
augumentPath
とupdateFlow
が分かれていますね。効率は落ちますがそれが無難か……
-
-
ABC 286 E (03/02)
- 昨日までの streak が、驚くほどの勇気を与えてくれる! 寝ないという選択を!
- Warshall-Floyd 法で解けば素直になりそうです。ド典型一発ネタみたいな問題は解きたいですね。
- 最悪計算量が
になるので、別に毎回 dijkstra で計算しても良いようです。O(N^3) -
Warshall-Floyd で AC.
Unbox
必須でした。 - Dijkstra の練習
-
鉄則 A69 (03/02)
- 二部マッチング。本日は
maxFlow
のやり直しで AC したいと思います。 - 人 → 席の辺が与えられます。始点と終点を加えて
maxFlow
- うおお一瞬で AC! 今日は簡単でもいいか……
- 二部マッチング。本日は
-
鉄則 A70 (03/02)
- いいや解答 AC でもう 1 問解く!
- まずはグラフ問題に帰着する (敗北) 。
- 状態遷移のグラフで 2 点間の最小距離を求めます。 BFS かな
- AC!
-
ABC 282 D (03/03)
- コレを解こう!読解が終われば、主に頂点彩色問題です。
- DFS しながらすべての辺をあたれば、
で彩色できる。思い切るね! (けんちょん本)O(n + m) - 1 つでも 2 部グラフでないならば全体も 2 部グラフではない。なるほど
- DFS で重複したルートを通っていて重かったようです。グラフむずい……けど典型は大体回ったかもしれません。
-
ABC 281 D (03/04)
- DP か!
fold
型! - この考察成功は嬉しい。
- DP しても TLE.
で通るはずでは……?O(NKD) -
traceShow
を消したら爆速になって AC できました。よしよし
- DP か!
-
ABC 280 D (03/04)
- 昔解答を読んだけれど AC できなかった問題です。
- 素因数分解して少し計算を頑張ります。
- ループをサクッと書けるようになりたいですね。
-
ABC 277 D (03/04)
- 昔解答を読んだけれど AC できなかった問題。
-
は大きすぎるので配列を用意することができません。M <= 10^9 - HT.groupBy を使って AC 。
- 特定の腕力・ツールを手に入れると昔解けなかった問題も解けるようになりますね。
-
ABC 277 E (03/05, 03/06)
- 特殊な BFS を実装します。実装が重い……!
- WA, TLE, MLE, RE をすべて喰らいました。いやはや。。
- 色々やって失敗。 6 時間以上かかりそうです。。飛ばすかこれは
- ストリークは切れるけれど寝ます……
- 辺への情報付与のため、
サイズの表を作っていました。これが原因か……! さらにN^2 Set
をIntSet
に強引に替えてなんとか AC 。 - データを 2 種類に分ける解答が上手いですね。
- 残りの時間で BFS や DFS を短く書けるように訓練したいです。
- 関数を分けるよりも
where
で変数を増やす -
IS.union
を上手く使えば BFS でfoldl'
する必要は無い
- 関数を分けるよりも
-
ABC 259 D (03/07)
- 素直な Union-Find です。今日はこれをやってサクッと寝ます!
- 中学数学をサクッと正当できなくてショックではある…… (3 WA)
- 円の中心の距離が内接する境界を考えて
ですね……d^2 >= (r_2^2 - r_1^2) - AC. 今や単純に頭が悪くて悲しい (紙と鉛筆に頼ろう)
-
ABC 276 E (03/08)
- 素直な Union-FInd 。なんでこれが E 問題で緑 diff なんでしょう……?
-
ABC 273 D (03/09)
- とにかく盤面が広いので、 sparse にデータを持ちます。ある意味典型です。
-
IM.IntMap IS.IntSet
を 2 本持って壁の位置を管理して AC 。実装は中々重かったのですが、よく通しましたね自分。手数が揃えばこういう AC が出るようになる……!i - 入力をリストから
Vector
に変えると TLE しがちでした。 Bang pattern は積極的に使ったほうが良いですね 。 より詳しくはこちら 。
-
ABC 272 D (03/10)
- 全探索で下処理すれば、後は BFS です。
- ふははこれが腕力
-
ABC 263 D (03/10)
- 左右の走査をセグ木に入れてみましたが TLE
-
!
をつけまくった結果 圧倒的高速化で AC 。 最近このパターンが多いです 。 - セグ木じゃなくて最小値を配列に入れておけば十分ですね。
-
ABC 292 E (03/11)
- DFS で AC できない………… 謎の実力不足です
- Union-Find で AC
-
ABC 269 E (03/12)
- 見るからに 2 分探索。それでも解けないのが茶色の実力。。
-
(x, y)
を出力していてハマっていました。こういうハマり方から脱出できれば、二ヶ月以内には緑になるでしょう。
-
ABC 260 D (03/13)
- 何だか木を使う普通の計算問題でした。
- 自分で
の場合をコーナーケースに設定して間違えていました。とても悲しい。K = 1
グラフ関連を解き直したいです。 DFS, BFS, Dijkstra 最小カット、最大流、 2 部マッチング、 Bellman-Ford, Warshal-Floyd などなど……。意外と DP が少ないので DP も欲しい。
EDPC (F ~ Z)
緑 diff streak を終えたので、今度はグラフ・ DP に絞って解ける問題の幅を広げていきます。
-
☆ EDPC F
- 鉄則本で解いたことがあったので AC.
- 2 系列からの読み出しの方法は、片側から 1 文字捨てる or 一致する文字を同時に取り出す
- Backtracking は根性で。
- これがサクっと書けたらいいよなぁ 。もう少し簡単になりませんかね……!!
-
- グラフで配る DP
- フハハ解けるぞ
-
- DP 感の無いやつ。なんか解いたことがある気が……?
-
- スタートからゴールまでの道筋の数
- G 同様に配る DP. 算数でやっていたやつが一応 DP だったのですね
-
☆ EDPC I
- fold 型の DP です。
- 裏の数の確率を
まで持っておいて余事象\lfloor \frac m 2 \rfloor
-
☆ EDPC J
- 期待値! 確率が出てくると途端に難しく感じます。
- 状態遷移の図を書いて、逆向きに期待値が流れ込むように捉えます。
- 2 日かけて AC. 類題も解きました。得意になったかも
- メモ化再帰には限界があるので、単純なループで解けるとなお良いですね。
-
- ゲームは苦手……
-
- ゲームは苦手……
-
- fold 型の DP です。
- 部分問題の計算に累積和を使います。番兵が苦手……!
-
☆ EDPC N
- 区間 DP. むずいねぇ!
- 部分問題は、小区間のマージもしくは新しい区間の作成のような形で捉えます。
- 類題も解きました が TDPC の問題が難しかったです。
-
☆ EDPC O
- 集合を
Int
(bit 列) で表す bit DP! - fold 的な考え方だと計算量を減らすことができません。
- 結局今までで一番難しい DP だったかもしれません。身についた気がしない……
- 集合を
-
☆ EDPC P
- 考察失敗
- 幾何的な fold 型 DP ……ではなくすべての末端からの影響が及ぶ。
- 受け取る DP, 再帰か! 木なのでメモは要りません。
parent
だけ持ちます。 - マージ部分が掛け算なのが当然ではあるものの詰めて考えるのが難しい……
- もっと演習が必要です。 TDPC でリベンジしたい
-
- LIS と同じタイプの問題です
- 区間木を使うと簡単です
-
- ダブリング感はありますが全然考察できません。
-
- fold でいけそう……?
- いえ、上限 K が 999 のようなキリの良い値ではないので単純に行きません
- ううむ
-
- N - 1 個の不等式を満たす順列の数を求めよ。余事象で数え上げできないか……?
-
- 集合といえば bit DP だが、集合の集合を考える必要があり……?
- どう部分問題に分ければ良いか
-
- 解けたい問題……
-
- 分からない。。
-
- 取り方の順番が影響するナップサック問題。えぇ……
-
- 2 次元の座標圧縮か。
-
- ロングジャンプが可能。
じゃダメだよなぁ……N!
- ロングジャンプが可能。
PAST 過去問
PAST 2020-04
-
K
()
のバランスというシンプルな DP.(
と)
の数が揃えば良いわけではなく、)(
などは禁止ですね……
PAST 2021-09 (第 8 回)
11/15 まで自力で解けました。尺取り法は AC したかったな……
-
☆ H 最短経路
木の上で長さ K の経路が存在するか。 DFS しつつ、累積和の中にK - len
が含まれるかを調べます。全頂点から (もしくはすべての葉から) DFS して答えが求まります。 -
I /2 and *3
やるだけ。 -
J 数列の反転
まあセグ木ですよね。 -
☆ L 数列の反転
考察はできたんですが実装がダメでした。尺取り法。 -
M バランス
PAST 本に解説は載っていますが相当難しくないか…… -
N ジグサグな数列
楽勝! -
O 色分けトーナメント
これは全然わからない。
水 diff streak
解説 AC でいいから ABC を埋めていきます。 AtCoder Tags などで類題を解いていくと、解説 AC がスムーズですね。あまり実力向上に寄与しないかもしれませんが……。
AC streak
-
ABC 296 E (04/02)
読解力……! Union-Find で閉路を見つけましたがscc
を作っておくべきかもしれません。 →scc
を実装しました。 SCC の第一段階はトポロジカルソートですね。 -
☆ ABC 245 F (04/07)
基礎力が足りない……! -
☆ ABC 035 D (04/08)
考察は半分までいけましたが、計算量を落とせませんでした。なるほど良問……! この典型は習得できたと思います。 -
☆ ABC 289 E (04/08)
ハッハッハ! 全然解けませんな! ハッハッハッハッハ -
ABC 284 E (04/09)
Rolling hash の実装で繰り返し二乗法を使うと TLE, 位取りを見直して AC. 鉄則本を深くやっておけばな回でしたね。 -
☆ ABC 290 E (04/11)
数学 A 的なセンスが問われます。 2 段階計算量を削減します。経験で乗り切りたい。。 -
☆ ABC 270 E (04/12)
平易な良問。ぐああああああああ -
☆ ABC 281 E (04/12)
冷静に場合分けして AC. テストケースが弱く、修正途中で通ってしまいました。 Haskell でキューと言えばSeq
なのですが、使い勝手が良くないですね。 -
ABC 287 E (04/13)
置換は配列で表現できます。逆方向から解けば (ではなくO(N) と言うらしい) ですね。昔見た解説が今効いてきていると思うべきか……O(N + M) -
ABC 275 E (04/13)
普通の DP, と思いきや折返しをループと勘違いしていました。手続き型で累積和に難儀、VU.fold
にて考察ミスと重かったです。普通に解けるべき初歩的問題なのですが……! -
ABC 291 F (04/15)
普通に解法を思いついたのですが、 TLE になるんでしょうか……? → 普通に通りました。うーん単体で見ると緑 diff では。 → 解説だと DP でした。 DP として解くと辛そう…… -
☆ ABC 264 E (04/15)
グラフからの辺の削除ってどうやるんだ……。という問題ではなかった。ぐおおおおおお自力で解けるべきだった……! しかし Haskell としては実装が重めです。もう少し便利な Union-Find を用意しておくべきかもしれません。 -
ABC 298 E [04/16]
foldl'
, もといscanl'
で解けるごく普通の DP なのですがバグらせてしまう。。コンテスト中に AC するのは非常に厳しいですね。そろそろ解答の精度を上げていきたい。典型 90 をサクサク解けるようにならねば。
AC streak (再開)
手札も揃ったので、水 diff を 100 問解けば水色に届く気がします。
-
☆ ABC 257 D [04/21]
クラスカル法で WA. 確かに有方向グラフじゃないか……! 有向グラフの最小全域木を求めるアルゴリズムがあれば……あっても始点の取り方で悩みそうです。
始点の取り方で悩まずとも全点試せば良いのです! Brute-force 2 分探索 で AC. このパターン最頻出だと思います。 -
☆ ABC 274 [04/22]
グラフとして考察失敗。 Bit DP だったか……! どの道制限時間以内に解けません。 -
☆ ABC 272 E [04/24]
Mex! 数列を加工するのではなく、各項から各数列への寄与を元に数列の列を生成します。主客転倒的な趣の有る問題でした。むずい…… -
☆ ABC 271 E [04/25]
グラフの問題として解きたかったのですが、前準備があまりに煩雑で難しい。 大苦戦です 。が、解答を見ると DP でした。ほな fold か!IntSet
の fold で解くと、制約が緩いためナップサック問題よりも簡単になりました。ナップサック問題ならIntSet
は TLE になりますからね。ふーむフローチャートに入れておこう…… -
ABC 265 E [04/26]
解けました! しかしコンテスト時間内には到底解けません。 TLE を取るのも大変でした。よく復習しよう…… -
ABC 266 E [04/27]
寝かせたら解けました。試行をやり直すと結果が良くなる、すなわち期待値が高まるという題材は広く知られていますが、改めて数学的に検証できました。 -
ABC 261 E [04/28]
考察はほぼ合っていましたが、あと一歩およばず……! 類題は問題なく解けるでしょう。 -
ABC 246 E [04/29]
実行時間の制限がキツい。実装時間の制限も辛い。公式解説によると、グラフをダブらせて 0-1 BFS ということなので、それも実装してみたいと思います。 -
ABC278 F
入力を 1 つづつ処理する普通の DP に見えます。入力値の幅が狭いので、セグ木を併用すれば問題なしか。 PAST で普通に出てくる程度の問題に見えます。
重い
-
☆ ABC 268 D
再帰的なケース作成が辛い。まだこのパターンのメンタルモデルが無いですね。
精選 100 問
典型 90 問とは別に分野別で演習できるのは良いですね。
TODO
Atcoder Library Practical Contest
最近 Chokudai Speedrun の転倒数が出たので、典型収集の重要性を認識しています。問題は、出るのか以上に習得が間に合うか……!
- C: 原点と一次関数に挟まれた格子点の数に相当する そうです。
典型アルゴリズム問題集
PAST 中級本の練習問題集らしいです。躓いたら中級本も買っていいかもしれません。が、鉄則本 + EDPC の経験でサクッと解けそうな気がしています。
- A: 2 分探索
- B: 区間スケジューリング
-
C: 巡回セールスマン問題
集合 DP は配る DP が基本! - D: 単一始点経路
-
E: 全点最短経路
- フロイドワーシャル
- Dijkstra で
- F: 最小全域木
締め
日報に移動します