ABC 362 / Splay tree

Jul 14, 2024

ABC 362

ABC 362 に参加しました。

問題A 問題B 問題C 問題D 問題E 問題
予想1003006008,001,500
実際12665216341225
Table 1: 体感 Diff

A 問題

指定色以外のペンの min を取ります。

main=interact$show.(f.map read.init<*>last).words;f[r,g,b](c:_)|c=='R'=0+min g b|c=='G'=min b r|0<1=min r g

B 問題

三平方の定理かベクトルの内積で直角三角形を判定します。内積は ab=iaibi=abcos((ab))a \cdot b = \sum_i a_i b_i = |a| |b| \cos (\angle (a - b)) より a0,b0|a| \ne 0, |b| \ne 0 ならば iaibi=0\sum_i a_i b_i = 0 で直角を判定できます。

前回も幾何でした。珍しい。

C 問題

Haskeller 的には難問です。解説も難しいのですが、一応書きました。

{[dli,dri]}i\{[\mathcal{dl}_i, \mathcal{dr}_i]\}_i が与えられ、各ステップでは [dli,dri][\mathcal{dl}_i, \mathcal{dr}_i] の範囲内で移動します。 inRange (sum dls, sum drs) 0 が成り立つならば 0 で停止できます。 0 で停止できるなら、各ステップの移動量を決めて行きます。

まず各ステップ iidli\mathcal{dl}_i だけ移動するものとして、 0 に近づくために必要な分だけ dli\mathcal{dl}_idri\mathcal{dr}_i に近づけていきます。この差分を deltai\mathcal{delta}_i とすると、 deltai\mathcal{delta}_i 列を求めて dli\mathcal{dl}_i 列に加算することで解答できます。

deltai\mathcal{delta}_i を決めるためには、必要な正方向への移動量を状態に持って {[dli,dri]}i\{[\mathcal{dl}_i, \mathcal{dr}_i]\}_i を走査すれば良いです。リストならば mapAccumL が、 vector ならば mapM + State モナドが使えます。再帰関数を作っても良いですね。

D 問題

v1v_1 と各点間の最短距離を求めます。 Dijkstra 法を実施したいのですが、辺と頂点の両方に重みがあります。解法は 2 つあります:

  1. 頂点の重みを辺の重みに繰り込む
    辺を作るときに頂点の重みを加算しておきます。
  2. 頂点倍化
    evima さんの解説 です。 vin,voutv_{in}, v_{out} に分けることで、頂点の重みを辺に組み込めるとか。

Dijkstra 法は鉄則本やけんちょん本に載っています。枝刈りをお忘れなく!

E 問題

辛い……。 2 ms ってどういうことなんですか?!

G 問題

苦手な文字列アルゴリズムです。 Suffix Array は最近作ったので upsolve したいです。

Misc

Splay tree

Link/cut tree に必要という splay tree を実装しました。

主な情報源

以下から学びました。

用途

用途はありません。今のところ ただの遅い木 です。遅延評価とか link/cut tree にして初めて使い道が生まれるのかと思います。 Link/cut tree への期待が高まります。

なぜ vectorconcat / concatMapO(N)O(N) なのか

vectorconcatO(N)O(N) と書かれています。単純に (++) で畳み込むわけではないようです。たとえば (++) を使って長さ 11 の配列の NN 個畳み込む場合、長さ 2,3,,N2, 3, \dots, N の配列が生成されて最悪計算量は O(N2)O(N^2) になります。

結論

MVector を経由して上手いことやっています。

  1. concat の場合
    最終配列長が事前に分かるとしています。長さ n の配列を作って埋めていきます。
  2. concatMap の場合
    最終配列長が事前に分からないものとしています (なぜ?) 。適当な長さの配列を作り、サイズを倍々にして行くようです。 concatMapO(N)O(N) で済むのは、 Rust における N 回の Vec::push 呼び出しが O(N)O(N) なのと似ています。実際、長さ 1,2,4,,2k1, 2, 4, \dots, 2^k の配列を生成した場合、高々 4N4N 程度のアロケーションしかありませんから、 O(N)O(N) です。

unstream

concat の中身を見ると、 New.unstream によって実装されています。 unstream 処理を追っていきましょう。

-- | /O(n)/ Concatenate all vectors in the list.
concat :: Vector v a => [v a] -> v a
{-# INLINE concat #-}
concat = unstream . Bundle.fromVectors
concat の関数呼び出しを辿る
-- | /O(n)/ Construct a vector from a 'Bundle'.
unstream :: Vector v a => Bundle v a -> v a
{-# INLINE unstream #-}
unstream s = new (New.unstream s)

-- | Construct a vector from a monadic initialiser.
new :: Vector v a => New v a -> v a
{-# INLINE_FUSED new #-}
new m = m `seq` runST (unsafeFreeze =<< New.run m)
Listing 1: Generic.hs

New.unstreamMVector.vunstream を呼んでいます。

-- ほぼ `MVector`
data New v a = New (forall s. ST s (Mutable v s a))

unstream :: Vector v a => Bundle v a -> New v a
{-# INLINE_FUSED unstream #-}
unstream s = s `seq` New (MVector.vunstream s)
Listing 2: New.hs

さらに追いかけます。

-- | Create a new mutable vector and fill it with elements from the 'Bundle'.
-- The vector will grow exponentially if the maximum size of the 'Bundle' is
-- unknown.
vunstream :: (PrimMonad m, V.Vector v a)
         => Bundle v a -> m (V.Mutable v (PrimState m) a)
-- NOTE: replace INLINE_FUSED by INLINE? (also in unstreamR)
{-# INLINE_FUSED vunstream #-}
vunstream s = vmunstream (Bundle.lift s)
Listing 3: Mutable.hs

結局 Mutable モジュールの vmunstream を呼び出しており、これは bundle の最大サイズが事前に分かるかで分岐しています。

-- | Create a new mutable vector and fill it with elements from the monadic
-- stream. The vector will grow exponentially if the maximum size of the stream
-- is unknown.
vmunstream :: (PrimMonad m, V.Vector v a)
           => MBundle m v a -> m (V.Mutable v (PrimState m) a)
{-# INLINE_FUSED vmunstream #-}
vmunstream s = case upperBound (MBundle.size s) of
               Just n  -> vmunstreamMax     s n
               Nothing -> vmunstreamUnknown s
Listing 4: Mutable.hs

サイズ n が事前に分かる場合は、長さ nMVector を作って埋めていきます。これが concat の場合です。

vmunsteramMax: bundle の最大サイズが事前に分かっている場合
vmunstreamMax :: (PrimMonad m, V.Vector v a)
              => MBundle m v a -> Int -> m (V.Mutable v (PrimState m) a)
{-# INLINE vmunstreamMax #-}
vmunstreamMax s n
  = do
      v <- checkLength Internal n $ unsafeNew n
      let {-# INLINE_INNER copyChunk #-}
          copyChunk i (Chunk m f) =
            checkSlice Internal i m (length v) $ do
              f (basicUnsafeSlice i m v)
              return (i+m)

      n' <- Stream.foldlM' copyChunk 0 (MBundle.chunks s)
      return $ checkSlice Internal 0 n' n
             $ unsafeSlice 0 n' v

サイズが不明の場合は、 MVector のサイズを倍々に増やしつつ埋めていきます。これが concatMap の場合です。

vmunstreamUnknown: bundle の最大サイズが事前に分からない場合
vmunstreamUnknown :: (PrimMonad m, V.Vector v a)
                 => MBundle m v a -> m (V.Mutable v (PrimState m) a)
{-# INLINE vmunstreamUnknown #-}
vmunstreamUnknown s
  = do
      v <- unsafeNew 0
      (v', n) <- Stream.foldlM copyChunk (v,0) (MBundle.chunks s)
      return $ checkSlice Internal 0 n (length v')
             $ unsafeSlice 0 n v'
  where
    {-# INLINE_INNER copyChunk #-}
    copyChunk (v,i) (Chunk n f)
      = do
          let j = i+n
          v' <- if basicLength v < j
                  then unsafeGrow v (delay_inline max (enlarge_delta v) (j - basicLength v))
                  else return v
          checkSlice Internal i n (length v') $ f (basicUnsafeSlice i n v')
          return (v',j)

concat の計算量

concat はサイズが Exact nBundle を作って unstream にかけています。

-- | /O(n)/ Concatenate all vectors in the list.
concat :: Vector v a => [v a] -> v a
{-# INLINE concat #-}
concat = unstream . Bundle.fromVectors

Fusion 関係のコードは読み込めていませんが、ひとまずサイズ指定の部分だけ見れば良いかと思います (Exact n です) 。

Data/Vector/Fusion/Bundle/Monadic.hs
fromVectors :: forall m v a. (Monad m, Vector v a) => [v a] -> Bundle m v a
{-# INLINE_FUSED fromVectors #-}
fromVectors us = Bundle (Stream pstep (Left us))
                        (Stream vstep us)
                        Nothing
                        (Exact n) -- ***** これ
  where
    n = List.foldl' (\k v -> k + basicLength v) 0 us

    pstep (Left []) = return Done
    pstep (Left (v:vs)) = basicLength v `seq` return (Skip (Right (v,0,vs)))

    pstep (Right (v,i,vs))
      | i >= basicLength v = return $ Skip (Left vs)
      | otherwise          = case basicUnsafeIndexM v i of
                               Box x -> return $ Yield x (Right (v,i+1,vs))

    -- FIXME: work around bug in GHC 7.6.1
    vstep :: HasCallStack => [v a] -> m (Step [v a] (Chunk v a))
    vstep [] = return Done
    vstep (v:vs) = return $ Yield (Chunk (basicLength v)
                                         (\mv -> check
                                                 Internal
                                                 "length mismatch"
                                                 (M.basicLength mv == basicLength v)
                                                 $ stToPrim $ basicUnsafeCopy mv v)) vs

concatMap の計算量

concatMap はサイズ UnknownBundle を作って unstream にかけています。

-- | Map a function over a vector and concatenate the results.
concatMap :: (Vector v a, Vector v b) => (a -> v b) -> v a -> v b
{-# INLINE concatMap #-}
-- NOTE: We can't fuse concatMap anyway so don't pretend we do.
-- ..
concatMap f = unstream
            . Bundle.concatVectors
            . Bundle.map f
            . stream

Bundle のサイズは Unknown です。

Data/Vector/Fusion/Bundle/Monadic.hs
concatVectors :: (Monad m, Vector v a) => Bundle m u (v a) -> Bundle m v a
{-# INLINE_FUSED concatVectors #-}
concatVectors Bundle{sElems = Stream step t}
  = Bundle (Stream pstep (Left t))
           (Stream vstep t)
           Nothing
           Unknown -- ***** これ
  where
    pstep (Left s) = do
      r <- step s
      case r of
        Yield v s' -> basicLength v `seq` return (Skip (Right (v,0,s')))
        Skip    s' -> return (Skip (Left s'))
        Done       -> return Done

    pstep (Right (v,i,s))
      | i >= basicLength v = return (Skip (Left s))
      | otherwise          = case basicUnsafeIndexM v i of
                               Box x -> return (Yield x (Right (v,i+1,s)))


    vstep s = do
      r <- step s
      case r of
        Yield v s' -> return (Yield (Chunk (basicLength v)
                                           (\mv -> check
                                                   Internal
                                                   "length mismatch"
                                                   (M.basicLength mv == basicLength v)
                                                   $ stToPrim $ basicUnsafeCopy mv v)) s')
        Skip    s' -> return (Skip s')
        Done       -> return Done

vconcatMapN

上記 concatMap において、 Bundle のサイズ指定を Unknown から Exact n に変更してみました。が、実行速度には無影響でした。

なぜでしょう? まあ問題無く使って行けそうです。

SNS 情報

Emacs

DTM

PC

先々週に買った PC ですが、購買を間違えました。やはりパソコンに疎いようです。

ギター (ど下手)

急に 160 BPS の 16 分音符が弾けるようになりました (単弦に限る) 。手首というより、指でピッキングして良いことを理解しました。たぶん。

トレモロの音が出せると色々遊べてデカいですが、どうでしょう。 SSD 到着待ちです。